Merge pull request #51 from igor04091968/docs/detmir-production-baseline-20260629
release-assets / sbom-and-release-assets (push) Canceled after 0s
rust-workspace / rust-workspace (push) Canceled after 0s
CI / Rust checks (push) Canceled after 0s
CI / Docs and registry checks (push) Canceled after 0s
CI / Smoke checks (push) Canceled after 0s
Coverage / Coverage baseline (push) Canceled after 0s
Security / Cargo audit (push) Canceled after 0s
Security / Cargo deny (push) Canceled after 0s
Security / Secret pattern check (push) Canceled after 0s
Security / Dependency review (push) Canceled after 0s

docs(detmir): record production restore baseline
This commit is contained in:
IgorRachkov
2026-06-29 13:56:17 +03:00
committed by GitHub
22 changed files with 1341 additions and 194 deletions
+26 -26
View File
@@ -6,43 +6,43 @@
# submissions. # submissions.
# Repository default owner. # Repository default owner.
* @igor04091968 * @igor04091968 @rachkovii68-bisness
# Rust workspace and crates. # Rust workspace and crates.
/adk-rust/ @igor04091968 /adk-rust/ @igor04091968 @rachkovii68-bisness
**/Cargo.toml @igor04091968 **/Cargo.toml @igor04091968 @rachkovii68-bisness
**/Cargo.lock @igor04091968 **/Cargo.lock @igor04091968 @rachkovii68-bisness
**/*.rs @igor04091968 **/*.rs @igor04091968 @rachkovii68-bisness
# Operational scripts and automation. # Operational scripts and automation.
/scripts/ @igor04091968 /scripts/ @igor04091968 @rachkovii68-bisness
# Product documentation, demo material and screenshots. # Product documentation, demo material and screenshots.
/docs/ @igor04091968 /docs/ @igor04091968 @rachkovii68-bisness
/docs/demo/ @igor04091968 /docs/demo/ @igor04091968 @rachkovii68-bisness
/docs/screenshots/ @igor04091968 /docs/screenshots/ @igor04091968 @rachkovii68-bisness
/docs/assets/screenshots/ @igor04091968 /docs/assets/screenshots/ @igor04091968 @rachkovii68-bisness
# Registry-readiness evidence and policy documents. # Registry-readiness evidence and policy documents.
/docs/registry/ @igor04091968 /docs/registry/ @igor04091968 @rachkovii68-bisness
# GitHub public mirror validation metadata and governance controls. # GitHub public mirror validation metadata and governance controls.
/.github/workflows/ @igor04091968 /.github/workflows/ @igor04091968 @rachkovii68-bisness
/.github/workflows/ci.yml @igor04091968 /.github/workflows/ci.yml @igor04091968 @rachkovii68-bisness
/.github/workflows/security.yml @igor04091968 /.github/workflows/security.yml @igor04091968 @rachkovii68-bisness
/.github/workflows/coverage.yml @igor04091968 /.github/workflows/coverage.yml @igor04091968 @rachkovii68-bisness
/.github/pull_request_template.md @igor04091968 /.github/pull_request_template.md @igor04091968 @rachkovii68-bisness
/.github/ISSUE_TEMPLATE/ @igor04091968 /.github/ISSUE_TEMPLATE/ @igor04091968 @rachkovii68-bisness
/.github/CODEOWNERS @igor04091968 /.github/CODEOWNERS @igor04091968 @rachkovii68-bisness
/docs/BRANCH_PROTECTION_POLICY_RU.md @igor04091968 /docs/BRANCH_PROTECTION_POLICY_RU.md @igor04091968 @rachkovii68-bisness
/docs/BRANCH_PROTECTION_EVIDENCE_RU.md @igor04091968 /docs/BRANCH_PROTECTION_EVIDENCE_RU.md @igor04091968 @rachkovii68-bisness
/docs/PR_REVIEW_WORKFLOW_RU.md @igor04091968 /docs/PR_REVIEW_WORKFLOW_RU.md @igor04091968 @rachkovii68-bisness
/docs/PR_REVIEW_EVIDENCE_RU.md @igor04091968 /docs/PR_REVIEW_EVIDENCE_RU.md @igor04091968 @rachkovii68-bisness
# Ansible deployment automation. # Ansible deployment automation.
/ansible/ @igor04091968 /ansible/ @igor04091968 @rachkovii68-bisness
# Security and contribution governance. # Security and contribution governance.
/SECURITY.md @igor04091968 /SECURITY.md @igor04091968 @rachkovii68-bisness
/CONTRIBUTING.md @igor04091968 /CONTRIBUTING.md @igor04091968 @rachkovii68-bisness
/deny.toml @igor04091968 /deny.toml @igor04091968 @rachkovii68-bisness
+78 -7
View File
@@ -7,9 +7,9 @@ use clap::Parser;
use reqwest::blocking::Client; use reqwest::blocking::Client;
use serde_json::Value; use serde_json::Value;
const DEFAULT_SERVER: &str = "http://192.0.2.13:5600"; const DEFAULT_SERVER: &str = "http://10.10.10.13:5600";
const DEFAULT_HOST: &str = "HOST-EXAMPLE"; const DEFAULT_HOST: &str = "SHARKON2025";
const DEFAULT_RDP_HOST: &str = "198.51.100.18"; const DEFAULT_RDP_HOST: &str = "192.168.100.19";
const BUCKETS: &[(&str, &str)] = &[ const BUCKETS: &[(&str, &str)] = &[
("aw-watcher-afk", "AFK watcher"), ("aw-watcher-afk", "AFK watcher"),
("aw-watcher-window", "Window watcher"), ("aw-watcher-window", "Window watcher"),
@@ -38,6 +38,9 @@ struct Cli {
#[arg(long)] #[arg(long)]
no_color: bool, no_color: bool,
#[arg(long, default_value_t = true)]
dlp_enabled: bool,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -119,7 +122,37 @@ fn main() {
} }
fn run() -> Result<i32> { fn run() -> Result<i32> {
let cli = Cli::parse(); let mut cli = Cli::parse();
if cli.server == DEFAULT_SERVER {
if let Some(value) = env_nonempty("CHECK_AW_FULL_SERVER")
.or_else(|| env_nonempty("AW_SMOKE_AW_SERVER"))
.or_else(|| env_nonempty("AW_SERVER"))
{
cli.server = value;
}
}
if cli.host == DEFAULT_HOST {
if let Some(value) = env_nonempty("CHECK_AW_FULL_HOST")
.or_else(|| env_nonempty("AW_SMOKE_SOURCE_HOSTNAME"))
.or_else(|| env_nonempty("AW_LOGICAL_HOST_ID"))
.or_else(|| env_nonempty("AW_MONITORED_WINDOWS_HOSTNAME"))
{
cli.host = value;
}
}
if cli.rdp_host == DEFAULT_RDP_HOST {
if let Some(value) = env_nonempty("CHECK_AW_FULL_RDP_HOST")
.or_else(|| env_nonempty("AW_SMOKE_WINDOWS_HOST"))
.or_else(|| env_nonempty("AW_WINDOWS_HOST"))
{
cli.rdp_host = value;
}
}
if let Some(value) =
env_nonempty("AW_DLP_ENABLED").or_else(|| env_nonempty("DETMIR_DLP_ENABLED"))
{
cli.dlp_enabled = parse_env_flag(&value);
}
let server = cli.server.trim_end_matches('/').to_string(); let server = cli.server.trim_end_matches('/').to_string();
let colors = Colors::new(!cli.no_color && std::env::var_os("NO_COLOR").is_none()); let colors = Colors::new(!cli.no_color && std::env::var_os("NO_COLOR").is_none());
let timeout = Duration::from_secs(cli.timeout_seconds.max(1)); let timeout = Duration::from_secs(cli.timeout_seconds.max(1));
@@ -182,7 +215,11 @@ fn run() -> Result<i32> {
); );
let mut rows = Vec::new(); let mut rows = Vec::new();
for (bucket, label) in BUCKETS { for (bucket, label) in BUCKETS
.iter()
.copied()
.filter(|(bucket, _)| cli.dlp_enabled || !bucket.starts_with("aw-dlp-"))
{
let row = read_bucket_row(&client, &server, &cli.host, bucket, label, now, &context); let row = read_bucket_row(&client, &server, &cli.host, bucket, label, now, &context);
println!( println!(
" {:<42} {:<8} {:<20} {}", " {:<42} {:<8} {:<20} {}",
@@ -193,6 +230,15 @@ fn run() -> Result<i32> {
); );
rows.push(row); rows.push(row);
} }
if !cli.dlp_enabled {
println!(
" {:<42} {:<8} {:<20} {}",
"DLP buckets",
"-",
"disabled",
colors.paint(colors.cyan, "SKIPPED")
);
}
println!(); println!();
println!( println!(
@@ -330,7 +376,12 @@ fn read_bucket_row(
status: BucketStatus::Unknown, status: BucketStatus::Unknown,
}; };
}; };
let age_sec = (now - ts).num_seconds().max(0); let effective_ts = if bucket == "aw-watcher-afk" {
bucket_metadata_end(client, server, &bucket_full).unwrap_or(ts)
} else {
ts
};
let age_sec = (now - effective_ts).num_seconds().max(0);
BucketRow { BucketRow {
label, label,
last_id, last_id,
@@ -407,6 +458,15 @@ fn latest_event(client: &Client, server: &str, bucket: &str) -> Result<Option<Va
Ok(value.as_array().and_then(|items| items.first()).cloned()) Ok(value.as_array().and_then(|items| items.first()).cloned())
} }
fn bucket_metadata_end(client: &Client, server: &str, bucket: &str) -> Option<DateTime<Utc>> {
let url = format!("{server}/api/0/buckets/{bucket}");
let value = get_json(client, &url).ok()?;
value
.pointer("/metadata/end")
.and_then(Value::as_str)
.and_then(parse_ts)
}
fn get_json(client: &Client, url: &str) -> Result<Value> { fn get_json(client: &Client, url: &str) -> Result<Value> {
client client
.get(url) .get(url)
@@ -423,7 +483,7 @@ fn check_cors(client: &Client, server: &str) -> u16 {
let url = format!("{server}/api/0/settings/"); let url = format!("{server}/api/0/settings/");
client client
.get(&url) .get(&url)
.header("Origin", "http://192.0.2.13:5600") .header("Origin", server)
.send() .send()
.map(|response| response.status().as_u16()) .map(|response| response.status().as_u16())
.unwrap_or(0) .unwrap_or(0)
@@ -466,6 +526,17 @@ fn json_value_to_string(value: &Value) -> String {
} }
} }
fn env_nonempty(name: &str) -> Option<String> {
std::env::var(name).ok().filter(|value| !value.is_empty())
}
fn parse_env_flag(value: &str) -> bool {
matches!(
value.trim().to_ascii_lowercase().as_str(),
"1" | "true" | "yes" | "on"
)
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
+25 -11
View File
@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# check-aw-full.sh - Полная проверка ActivityWatch: сервер + RDP-хост # check-aw-full.sh - Полная проверка ActivityWatch: сервер + RDP-хост
# Сервер: 192.0.2.13:5600 # Сервер: http://10.10.10.13:5600
# RDP-хост: 198.51.100.18 (HOST-EXAMPLE) # RDP-хост: 192.168.100.19 (logical host id SHARKON2025)
if [[ "${CHECK_AW_FULL_FORCE_LEGACY:-0}" != "1" ]]; then if [[ "${CHECK_AW_FULL_FORCE_LEGACY:-0}" != "1" ]]; then
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -16,12 +16,17 @@ if [[ "${CHECK_AW_FULL_FORCE_LEGACY:-0}" != "1" ]]; then
done done
fi fi
SERVER="http://192.0.2.13:5600" SERVER="${CHECK_AW_FULL_SERVER:-${AW_SMOKE_AW_SERVER:-${AW_SERVER:-http://10.10.10.13:5600}}}"
HOSTNAME_FILTER="HOST-EXAMPLE" HOSTNAME_FILTER="${CHECK_AW_FULL_HOST:-${AW_SMOKE_SOURCE_HOSTNAME:-${AW_LOGICAL_HOST_ID:-${AW_MONITORED_WINDOWS_HOSTNAME:-SHARKON2025}}}}"
RDP_HOST="198.51.100.18" RDP_HOST="${CHECK_AW_FULL_RDP_HOST:-${AW_SMOKE_WINDOWS_HOST:-${AW_WINDOWS_HOST:-192.168.100.19}}}"
NOW=$(date -u +%s) NOW=$(date -u +%s)
HOST_INACTIVE=false HOST_INACTIVE=false
GUARD_HEALTHY=false GUARD_HEALTHY=false
DLP_ENABLED="${AW_DLP_ENABLED:-${DETMIR_DLP_ENABLED:-true}}"
case "${DLP_ENABLED,,}" in
0|false|no|off) DLP_ENABLED=false ;;
*) DLP_ENABLED=true ;;
esac
classify_bucket_age() { classify_bucket_age() {
local bucket="$1" local bucket="$1"
@@ -100,7 +105,7 @@ echo ""
# 1. Проверка сервера # 1. Проверка сервера
echo -e "${CYAN}--- 1. AW Server ($SERVER) ---${NC}" echo -e "${CYAN}--- 1. AW Server ($SERVER) ---${NC}"
echo -n " Connectivity... " echo -n " Connectivity... "
RESP=$(no_proxy=192.0.2.13 curl -s --connect-timeout 10 --max-time 15 "$SERVER/api/0/info" 2>&1) RESP=$(no_proxy='*' curl -s --connect-timeout 10 --max-time 15 "$SERVER/api/0/info" 2>&1)
if [ $? -eq 0 ] && echo "$RESP" | jq -e '.version' > /dev/null 2>&1; then if [ $? -eq 0 ] && echo "$RESP" | jq -e '.version' > /dev/null 2>&1; then
VERSION=$(echo "$RESP" | jq -r '.version') VERSION=$(echo "$RESP" | jq -r '.version')
echo -e " ${GREEN}OK${NC} (aw-server $VERSION)" echo -e " ${GREEN}OK${NC} (aw-server $VERSION)"
@@ -110,7 +115,7 @@ else
fi fi
echo -n " CORS... " echo -n " CORS... "
CORS_RESP=$(no_proxy=192.0.2.13 curl -s --connect-timeout 10 --max-time 15 -o /dev/null -w '%{http_code}' -H "Origin: http://192.0.2.13:5600" "$SERVER/api/0/settings/" 2>&1) CORS_RESP=$(no_proxy='*' curl -s --connect-timeout 10 --max-time 15 -o /dev/null -w '%{http_code}' -H "Origin: $SERVER" "$SERVER/api/0/settings/" 2>&1)
if [ "$CORS_RESP" = "200" ]; then if [ "$CORS_RESP" = "200" ]; then
echo -e "${GREEN}OK${NC}" echo -e "${GREEN}OK${NC}"
else else
@@ -119,7 +124,7 @@ fi
echo "" echo ""
# 1b. Context for inactive/event-driven classification # 1b. Context for inactive/event-driven classification
WORKTIME_EVENT_DATA=$(no_proxy=192.0.2.13 curl -s --connect-timeout 10 --max-time 15 "$SERVER/api/0/buckets/aw-worktime-sessions_$HOSTNAME_FILTER/events?limit=1" 2>&1) WORKTIME_EVENT_DATA=$(no_proxy='*' curl -s --connect-timeout 10 --max-time 15 "$SERVER/api/0/buckets/aw-worktime-sessions_$HOSTNAME_FILTER/events?limit=1" 2>&1)
WORKTIME_TS=$(echo "$WORKTIME_EVENT_DATA" | jq -r '.[0].timestamp // ""' 2>/dev/null) WORKTIME_TS=$(echo "$WORKTIME_EVENT_DATA" | jq -r '.[0].timestamp // ""' 2>/dev/null)
WORKTIME_ACTIVE=$(echo "$WORKTIME_EVENT_DATA" | jq -r '.[0].data.active // false' 2>/dev/null) WORKTIME_ACTIVE=$(echo "$WORKTIME_EVENT_DATA" | jq -r '.[0].data.active // false' 2>/dev/null)
if [ -n "$WORKTIME_TS" ]; then if [ -n "$WORKTIME_TS" ]; then
@@ -132,7 +137,7 @@ if [ -n "$WORKTIME_TS" ]; then
fi fi
fi fi
GUARD_EVENT_DATA=$(no_proxy=192.0.2.13 curl -s --connect-timeout 10 --max-time 15 "$SERVER/api/0/buckets/aw-rus-collector-guard_$HOSTNAME_FILTER/events?limit=1" 2>&1) GUARD_EVENT_DATA=$(no_proxy='*' curl -s --connect-timeout 10 --max-time 15 "$SERVER/api/0/buckets/aw-rus-collector-guard_$HOSTNAME_FILTER/events?limit=1" 2>&1)
GUARD_TS=$(echo "$GUARD_EVENT_DATA" | jq -r '.[0].timestamp // ""' 2>/dev/null) GUARD_TS=$(echo "$GUARD_EVENT_DATA" | jq -r '.[0].timestamp // ""' 2>/dev/null)
GUARD_STATUS=$(echo "$GUARD_EVENT_DATA" | jq -r '.[0].data.status // ""' 2>/dev/null) GUARD_STATUS=$(echo "$GUARD_EVENT_DATA" | jq -r '.[0].data.status // ""' 2>/dev/null)
GUARD_PROBLEMS=$(echo "$GUARD_EVENT_DATA" | jq -r '([.[0].data.problems[]?] | length) // 0' 2>/dev/null) GUARD_PROBLEMS=$(echo "$GUARD_EVENT_DATA" | jq -r '([.[0].data.problems[]?] | length) // 0' 2>/dev/null)
@@ -165,9 +170,12 @@ BUCKETS=(
for entry in "${BUCKETS[@]}"; do for entry in "${BUCKETS[@]}"; do
bucket="${entry%%|*}" bucket="${entry%%|*}"
label="${entry##*|}" label="${entry##*|}"
if [ "$DLP_ENABLED" = "false" ] && [[ "$bucket" == aw-dlp-* ]]; then
continue
fi
bucket_full="${bucket}_${HOSTNAME_FILTER}" bucket_full="${bucket}_${HOSTNAME_FILTER}"
EVENT_DATA=$(no_proxy=192.0.2.13 curl -s --connect-timeout 10 --max-time 15 "$SERVER/api/0/buckets/$bucket_full/events?limit=1" 2>&1) EVENT_DATA=$(no_proxy='*' curl -s --connect-timeout 10 --max-time 15 "$SERVER/api/0/buckets/$bucket_full/events?limit=1" 2>&1)
LAST_ID=$(echo "$EVENT_DATA" | jq '.[0].id // 0') LAST_ID=$(echo "$EVENT_DATA" | jq '.[0].id // 0')
LAST_TS=$(echo "$EVENT_DATA" | jq -r '.[0].timestamp // "no events"') LAST_TS=$(echo "$EVENT_DATA" | jq -r '.[0].timestamp // "no events"')
@@ -200,6 +208,9 @@ for entry in "${BUCKETS[@]}"; do
printf " %-42s %-8s %-20s %b\n" "$label" "$LAST_ID" "$AGE" "$STATUS" printf " %-42s %-8s %-20s %b\n" "$label" "$LAST_ID" "$AGE" "$STATUS"
done done
if [ "$DLP_ENABLED" = "false" ]; then
printf " %-42s %-8s %-20s %b\n" "DLP buckets" "-" "disabled" "${CYAN}SKIPPED${NC}"
fi
echo "" echo ""
# 3. Проверка RDP-хоста # 3. Проверка RDP-хоста
@@ -230,8 +241,11 @@ DEAD_COUNT=0
for entry in "${BUCKETS[@]}"; do for entry in "${BUCKETS[@]}"; do
bucket="${entry%%|*}" bucket="${entry%%|*}"
if [ "$DLP_ENABLED" = "false" ] && [[ "$bucket" == aw-dlp-* ]]; then
continue
fi
bucket_full="${bucket}_${HOSTNAME_FILTER}" bucket_full="${bucket}_${HOSTNAME_FILTER}"
EVENT_DATA=$(no_proxy=192.0.2.13 curl -s --connect-timeout 10 --max-time 15 "$SERVER/api/0/buckets/$bucket_full/events?limit=1" 2>&1) EVENT_DATA=$(no_proxy='*' curl -s --connect-timeout 10 --max-time 15 "$SERVER/api/0/buckets/$bucket_full/events?limit=1" 2>&1)
LAST_TS=$(echo "$EVENT_DATA" | jq -r '.[0].timestamp // "no events"') LAST_TS=$(echo "$EVENT_DATA" | jq -r '.[0].timestamp // "no events"')
if [ "$LAST_TS" != "no events" ] && [ -n "$LAST_TS" ]; then if [ "$LAST_TS" != "no events" ] && [ -n "$LAST_TS" ]; then
+74 -55
View File
@@ -1,87 +1,107 @@
# AWatch-rus: branch protection evidence # AWatch-rus: branch protection evidence
Дата: 2026-06-23 Дата: 2026-06-24
branch_protection_status: "pending_manual_verification" branch_protection_status: "verified_active_ruleset"
GitHub issue: https://github.com/igor04091968/AWatch-rus/issues/49 GitHub issue: https://github.com/igor04091968/AWatch-rus/issues/49
Этот документ является evidence template для ручной проверки GitHub branch Этот документ фиксирует maintainer-verified GitHub ruleset / branch protection
protection на публичном зеркале. Он не утверждает, что branch protection уже evidence для публичного зеркала. Подтверждение относится только к GitHub public
настроена или подтверждена. mirror governance.
## Target ## Target
- Repository: `igor04091968/AWatch-rus`. - Repository: `igor04091968/AWatch-rus`.
- Platform role: GitHub public mirror validation only. - Platform role: GitHub public mirror validation only.
- Protected branch: `main`. - Protected branch: `main`.
- Ruleset name: `main`.
- Enforcement: `active`.
- Policy source: `docs/BRANCH_PROTECTION_POLICY_RU.md`. - Policy source: `docs/BRANCH_PROTECTION_POLICY_RU.md`.
- Evidence owner: maintainer. - Evidence owner: maintainer.
## Settings To Verify ## Verified Settings
Maintainer должен вручную проверить, что для `main` configured rule включает: Maintainer manually verified the following GitHub UI state:
- require pull request before merging; - verification date: `2026-06-24`;
- require approvals: `1`; - maintainer: `maintainer`;
- dismiss stale approvals when new commits are pushed; - repository: `github.com/igor04091968/AWatch-rus`;
- require review from CODEOWNERS, if available on the current GitHub plan; - ruleset name: `main`;
- require status checks to pass before merging; - enforcement: `active`;
- require branches to be up to date before merging, if compatible with current - target branch: `main`;
maintainer workflow; - applies_to_targets: `1`;
- restrict force pushes; - bypass_list: `empty`;
- restrict deletions; - required pull request: `enabled`;
- administrator bypass decision documented, preferably disabled for stricter - required approvals: `1`;
mode. - dismiss stale pull request approvals when new commits are pushed: `enabled`;
- require review from Code Owners: `enabled`;
- block force pushes: `enabled`.
## Required Checks ## Verified Required Status Checks
Expected required checks are based on current workflow/job names: GitHub ruleset required status checks verified in the UI:
- `CI / Rust checks` - `Coverage baseline`
- `CI / Docs and registry checks` - `security`
- `CI / Smoke checks` - `rust-checks`
- `Coverage / Coverage baseline` - `docs-registry-checks`
- `Security / Cargo audit` - `smoke-checks`
- `Security / Cargo deny`
- `Security / Secret pattern check`
- `Security / Dependency review`
If GitHub displays a different context name, record the exact displayed name and These names are the ruleset-visible required check names from GitHub UI. They
update `docs/BRANCH_PROTECTION_POLICY_RU.md` before marking verification done. may differ from the human-readable workflow job names shown inside workflow
files.
## Manual Verification Procedure
1. Open repository settings for `igor04091968/AWatch-rus`.
2. Open branch protection or repository rules for branch `main`.
3. Compare enabled settings against this document and
`docs/BRANCH_PROTECTION_POLICY_RU.md`.
4. Verify required status-check names exactly as GitHub displays them.
5. Capture screenshot evidence without private account data or tokens.
6. Record evidence fields below.
7. Only after verification, update `branch_protection_status` from
`"pending_manual_verification"` to `"verified"` in a follow-up change.
## Evidence Record ## Evidence Record
- Screenshot filename placeholder: - Screenshot filename placeholder:
`docs/evidence/github-branch-protection-main-YYYY-MM-DD.png` `docs/evidence/github-ruleset-main-2026-06-24.png`
- Date: `YYYY-MM-DD` - Date: `2026-06-24`
- Maintainer: `maintainer` - Maintainer: `maintainer`
- Repository: `igor04091968/AWatch-rus` - Repository: `github.com/igor04091968/AWatch-rus`
- Ruleset: `main`
- Protected branch: `main` - Protected branch: `main`
- Required checks verified: `pending` - Enforcement: `active`
- Admin bypass decision: `pending` - Applies to targets: `1`
- Force-push restriction verified: `pending` - Bypass list: `empty`
- Deletion restriction verified: `pending` - Required checks verified: `Coverage baseline`, `security`, `rust-checks`,
- Notes: `pending` `docs-registry-checks`, `smoke-checks`
- Force-push restriction verified: `enabled`
- Notes: GitHub UI confirms active ruleset for target branch `main`.
## First Protected PR Validation
- PR URL: `https://github.com/igor04091968/AWatch-rus/pull/50`
- Linked governance issue: `https://github.com/igor04091968/AWatch-rus/issues/49`
- PR source branch: `docs/verified-github-ruleset-evidence`
- PR target branch: `main`
- Runtime/API/UI/product code changes: `none`
- Required checks status: `passed`
- Required checks: `Coverage baseline`, `security`, `rust-checks`,
`docs-registry-checks`, `smoke-checks`
- Review requirement status: `pending_review_required`
- Merge status: `open`
- Admin bypass used: `false`
- Outcome note: PR #50 confirms required checks execute under the active ruleset;
review/merge evidence is not yet complete.
## Future Reverification Procedure
1. Open repository settings for `igor04091968/AWatch-rus`.
2. Open repository rules/rulesets for `main`.
3. Confirm enforcement remains `active`.
4. Confirm target branch remains `main`.
5. Confirm bypass list remains empty.
6. Confirm required PR, approvals, stale dismissal, Code Owners review, status
checks and force-push block.
7. Capture screenshot evidence without private account data or tokens.
## Not Registry Release Evidence ## Not Registry Release Evidence
GitHub branch protection evidence is governance/process evidence for the public GitHub ruleset / branch protection evidence is public governance evidence only.
mirror. It is not registry release evidence and does not replace release It is not Russian registry release evidence and does not replace Russian
artifacts, checksums, build logs or release evidence from the Russian Gitea/build-runner release contour, release artifacts, checksums, build logs or
build-runner. release evidence from the Russian build-runner.
## Russian Contour Note ## Russian Contour Note
@@ -92,7 +112,6 @@ Russian build-runner. GitHub remains public mirror validation only.
- Do not record secrets, tokens, private URLs or account recovery details. - Do not record secrets, tokens, private URLs or account recovery details.
- Do not include private employee/customer data in screenshots. - Do not include private employee/customer data in screenshots.
- Do not claim branch protection is verified until maintainer evidence exists.
- Do not claim completed registry submission. - Do not claim completed registry submission.
- Do not claim certification. - Do not claim certification.
- Do not claim SIEM/DLP replacement. - Do not claim SIEM/DLP replacement.
+24 -19
View File
@@ -2,9 +2,8 @@
Дата: 2026-06-23 Дата: 2026-06-23
Статус: recommended policy. Этот документ описывает целевую настройку GitHub Статус: recommended policy plus verified GitHub ruleset evidence. GitHub UI
branch protection для публичного зеркала. Он не утверждает, что branch verification is recorded in `docs/BRANCH_PROTECTION_EVIDENCE_RU.md`.
protection уже включен.
GitHub остается public mirror validation surface. Primary registry-readiness GitHub остается public mirror validation surface. Primary registry-readiness
contour остается Russian Gitea plus Russian build-runner release evidence. contour остается Russian Gitea plus Russian build-runner release evidence.
@@ -12,6 +11,7 @@ contour остается Russian Gitea plus Russian build-runner release evidenc
## Scope ## Scope
- Branch: `main`. - Branch: `main`.
- Ruleset: `main`.
- Platform: GitHub public mirror. - Platform: GitHub public mirror.
- Purpose: visible review discipline, status-check discipline and public - Purpose: visible review discipline, status-check discipline and public
engineering maturity signal. engineering maturity signal.
@@ -36,10 +36,15 @@ contour остается Russian Gitea plus Russian build-runner release evidenc
Recommended settings for `main` on the GitHub public mirror: Recommended settings for `main` on the GitHub public mirror:
- Ruleset name: `main`.
- Enforcement: `active`.
- Target branches: `main`.
- Applies to: `1` target, `main`.
- Bypass list: empty.
- Require pull request before merging. - Require pull request before merging.
- Required approvals: `1`. - Required approvals: `1`.
- Dismiss stale approvals when new commits are pushed. - Dismiss stale approvals when new commits are pushed.
- Require review from CODEOWNERS if available on the current GitHub plan. - Require review from Code Owners.
- Require status checks to pass before merging. - Require status checks to pass before merging.
- Require branches to be up to date before merging if this does not block the - Require branches to be up to date before merging if this does not block the
current maintainer workflow. current maintainer workflow.
@@ -48,24 +53,24 @@ Recommended settings for `main` on the GitHub public mirror:
- Allow administrators bypass: documented decision only; stricter mode should - Allow administrators bypass: documented decision only; stricter mode should
keep bypass disabled unless repository recovery requires it. keep bypass disabled unless repository recovery requires it.
Recommended required checks, using current workflow/job names: Verified ruleset required status checks from GitHub UI:
- `CI / Rust checks` - `Coverage baseline`
- `CI / Docs and registry checks` - `security`
- `CI / Smoke checks` - `rust-checks`
- `Coverage / Coverage baseline` - `docs-registry-checks`
- `Security / Cargo audit` - `smoke-checks`
- `Security / Cargo deny`
- `Security / Secret pattern check`
- `Security / Dependency review`
Before verification, maintainer must compare these names with the exact check Workflow/job-name mapping for operator review:
names displayed by GitHub. If GitHub displays different names, update this
document and `docs/BRANCH_PROTECTION_EVIDENCE_RU.md` before recording evidence.
Current evidence status is tracked in - `Coverage baseline`: Coverage workflow baseline job.
`docs/BRANCH_PROTECTION_EVIDENCE_RU.md` and remains - `rust-checks`: CI Rust checks job.
`pending_manual_verification` until maintainer evidence is recorded. - `docs-registry-checks`: CI docs and registry checks job.
- `smoke-checks`: CI smoke checks job.
- `security`: security validation context shown by GitHub rulesets UI.
Current evidence status is `verified_active_ruleset` in
`docs/BRANCH_PROTECTION_EVIDENCE_RU.md`.
## Review expectations ## Review expectations
+325
View File
@@ -0,0 +1,325 @@
# DetMir/AWatch-rus: актуальное состояние и граница DLP-модуля
Дата фиксации: 2026-06-25.
Обновление 2026-06-29 после восстановления RDP-сервера: фактический
production IP Windows/RDP host теперь `192.168.100.19`; stable ActivityWatch
logical host id остаётся `SHARKON2025`. Подробный post-restore baseline:
`docs/DETMIR_RESTORE_BASELINE_2026-06-29_RU.md`.
Документ фиксирует фактическое состояние DetMir/AWatch-rus и первую границу
переработки горячего пути портала. Это не release evidence для реестра
российского ПО, не заявление о сертификации и не claim замены DLP/SIEM/EDR.
## Runtime baseline
- Контур: DetMir / AWatch-rus.
- Stable Windows/RDP logical host id для ActivityWatch bucket-ов и отчетов:
`SHARKON2025`. Это legacy logical id, а не обязательное физическое имя
Windows-сервера. Правило rename-safe эксплуатации описано в
`docs/WINDOWS_LOGICAL_HOST_ID_RU.md`.
- Текущий физический RDP/WinRM target: `192.168.100.19`.
- Portal host: `10.10.10.2`.
- Portal service: `detmir-portal.service`.
- Portal URL: `http://10.10.10.2:8720/`.
- Задеплоенный binary hash:
`653b22b0fbf29a22f7de42ade7b689490b1de16fa07e785e4e0efd3078e7a3bc`.
- Бэкап предыдущего binary на сервере:
`/usr/local/bin/detmir-portal.bak.20260625T045640Z`.
- Runtime mode после phase 1 deploy:
`DETMIR_PORTAL_DLP_MODULE_ENABLED=false`.
- Server-side optional DLP runtime control:
`AW_DLP_ENABLED=false|true` и `DETMIR_DLP_ENABLED=false|true`.
- Runtime control/statistics script:
`scripts/detmir_dlp_runtime_control.sh` / live
`/usr/local/bin/detmir-dlp-runtime-control`.
- Live DLP runtime state after 2026-06-25 controlled disable:
`AW_DLP_ENABLED=false`, `AW_DLP_INFLUX_ENABLED=false`;
active/enabled DLP units: `0/0`.
- Reason: DLP runtime materially increases Proxmox VM/LXC, InfluxDB, Grafana,
ClickHouse and AW server load. In production DetMir it is currently kept
disabled, but remains a documented optional module that can be enabled later.
- Health после деплоя: `/healthz` возвращал `status=ok`.
- Readiness после деплоя: `/readyz` возвращал `status=ready`.
## Что исправлено в текущем baseline
- Первичная загрузка портала больше не зависает в бесконечном `LOADING`.
- При холодном старте тяжелый операторский срез не блокирует UI бесконечно.
- Frontend показывает честное состояние `STALE / Первичный срез прогревается`.
- При prewarm больше не смешиваются статусы `STALE` и ложное
`Данные отсутствуют`.
- Progress bar доходит до `100%` в fail-soft/prewarm состоянии.
- Browser smoke после деплоя подтвердил:
- `loadStatus=STALE`;
- `progress=100%`;
- `LOADING=false`;
- `EMPTY=false`;
- `ERROR=false`.
## Phase 1 live verification
2026-06-25 после сборки `detmir-portal` и деплоя через
`ansible/deploy_detmir_portal.yml --limit proxmox` подтверждено:
- `/healthz`: `status=ok`;
- `/readyz`: `status=ready`;
- `/api/reports`: `ok=true`, `cache_status=warming`,
`modules.dlp.enabled=false`, `modules.dlp.hot_path=false`;
- `/api/operator`: `cache_status=warming`, `summary.severity=STALE`,
`modules.dlp.status=disabled`, `incidents=0`;
- server log для `/api/operator`: `status=200`, `latency_ms=49`.
Это подтверждает, что первый operator/API screen больше не блокируется на
холодной полной сборке. Полный snapshot строится в фоне и честно помечается как
`warming`/`STALE`.
## Текущая проблема производительности
Наблюдаемые признаки:
- после restart полный тяжелый snapshot может уходить в prewarm/stale mode;
- report cache и stale UI защищают пользователя от зависания, но не убирают
саму стоимость тяжелых расчетов;
- DLP evidence, screenshots, endpoint signals, case review и forensics
enrichment требуют больше CPU/IO/сетевых операций, чем Workforce core.
Вывод: DLP/evidence/forensics enrichment уже вынесен из обязательного hot path
phase 1 через `DETMIR_PORTAL_DLP_MODULE_ENABLED=false`, но полная оптимизация
тяжелого snapshot/prewarm остается отдельной инженерной задачей.
## Целевая граница после переработки
Core hot path:
- Workforce Operations;
- worktime/activity;
- загрузка, простои, перегруз;
- дисциплина процесса;
- качество и полнота данных;
- легкие агрегаты для руководителя;
- readiness/health без ожидания DLP.
Optional DLP module:
- DLP endpoint signals;
- clipboard/USB/print/web/file operation incidents;
- screenshots/evidence;
- DLP/case review;
- heavy security correlation;
- forensics timelines and evidence packages;
- Hayabusa enrichment where needed.
Отключение optional DLP module не должно ломать:
- `/healthz`;
- `/readyz`;
- первичную загрузку портала;
- Workforce views;
- management reports;
- базовый operator dashboard.
Отключение optional DLP module должно показывать честный статус:
```text
DLP module disabled / not configured
```
без заявления, что DLP-проверки выполнены.
## Рекомендуемые feature flags
Минимальная целевая модель конфигурации:
```json
{
"modules": {
"dlp": {
"enabled": false,
"evidence": false,
"correlation": false,
"screenshots": false,
"hot_path": false
}
}
}
```
Первая реализованная runtime-граница:
```text
--dlp-module-enabled
DETMIR_PORTAL_DLP_MODULE_ENABLED=true|false
AW_DLP_ENABLED=true|false
DETMIR_DLP_ENABLED=true|false
```
Default остается `true`, чтобы существующее поведение не менялось без явного
решения администратора. Для ускоренного Workforce/operator режима допускается
`DETMIR_PORTAL_DLP_MODULE_ENABLED=false`; в этом режиме портал:
- не читает DLP incident/case/review/audit файлы в основном report/operator
path;
- отключает security-events backend внутри snapshot, не меняя сохраненные
ClickHouse credentials;
- возвращает disabled-state для DLP evidence API;
- не считает отсутствие DLP ошибкой Workforce core.
Ansible-параметр поставки:
```yaml
detmir_portal_dlp_module_enabled_override: false
```
Отдельный `detmir-portal-evidence` сервис не отключается этим флагом и остается
самостоятельным контуром evidence/API при наличии отдельной конфигурации.
Hayabusa/Velociraptor boundary:
- Hayabusa/Sigma and Velociraptor are optional security findings / forensics
sources, not Workforce hot path dependencies.
- Heavy DLP runtime can remain disabled while Hayabusa/Velociraptor findings
are imported into Security Finding Inbox / ClickHouse.
- Velociraptor server/client mode must be enabled explicitly
(`disabled|offline_collector|server_clients`) and must not be auto-started by
routine production deploy on the small DetMir Proxmox contour.
- Findings from Velociraptor/Hayabusa support `decide -> plan -> approve ->
apply -> verify`, but do not imply automatic remediation without approval.
Server-side optional DLP runtime описан отдельно:
- [DLP_OPTIONAL_RUNTIME_RU.md](DLP_OPTIONAL_RUNTIME_RU.md).
При `AW_DLP_ENABLED=false`:
- `dlp-health-check` возвращает штатный `dlp:mode=disabled`;
- `detmir-dlp` не выполняет SSH health probe;
- `detmir-check`, `check-aw-full` и `check-aw-data` не считают DLP buckets
обязательными;
- `detmir-readiness` не требует DLP Influx write и DLP systemd units;
- перед отключением и после отключения собираются JSON-срезы в
`/var/lib/activitywatch/health/dlp-runtime-history/`, latest-срез остается в
`/var/lib/activitywatch/health/dlp-runtime-state.json`.
Live disable evidence 2026-06-25:
- `dlp-health-check` returned `ok=true`, `dlp:mode=disabled`;
- `detmir-dlp` returned `ok=true`, `dlp:mode=disabled`;
- pre-disable active units:
`aw-dlp-influx-exporter.timer`,
`activitywatch-dlp-aggregator.timer`,
`aw-dlp-report-scheduler.timer`,
`aw-dlp-syslog-forwarder.timer`,
`aw-dlp-webhook-sender.timer`,
`aw-dlp-cef-exporter.timer`,
`aw-dlp-ioc-refresh.timer`,
`aw-dlp-policy-engine.service`,
`aw-dlp-case-management.service`,
`detmir-portal-evidence.service`;
- post-disable active/enabled DLP units: `0/0`;
- retained evidence files:
`/var/lib/activitywatch/health/dlp-runtime-history/dlp-runtime-current-20260625T083619Z.json`,
`/var/lib/activitywatch/health/dlp-runtime-history/dlp-runtime-pre_disable-20260625T083637Z.json`,
`/var/lib/activitywatch/health/dlp-runtime-history/dlp-runtime-disabled-20260625T083700Z.json`;
- `check-aw-full` with `AW_DLP_ENABLED=false` reports `DLP buckets ...
SKIPPED`;
- ActivityWatch core services remained active:
`activitywatch-server`, `aw-worktime-api`.
Separate live observations after DLP disable, before 2026-06-29 restore:
- `aw-watcher-afk`, `aw-watcher-window` and `aw-worktime-sessions` were stale
in the manual check and require separate RDP collector/session recovery;
- WinRM from the server side to `192.168.100.18:5985` was unreachable during
this check;
- these are not treated as DLP disable regressions.
Post-restore correction on 2026-06-29:
- current physical RDP/WinRM target is `192.168.100.19`;
- laptop route to `192.168.100.19` is through DetMir OpenVPN gateway
`10.0.13.1`;
- WinRM `5985` and RDP `3389` are reachable from the admin laptop;
- stable ActivityWatch logical host id remains `SHARKON2025`.
Fail-safe правила:
- если DLP выключена, Security/Forensics views должны показывать disabled-state,
а не падать;
- если DLP включена, тяжелые DLP операции должны выполняться асинхронно или
через cache, а не блокировать первый Workforce/operator screen;
- readiness не должен заявлять DLP healthy, если модуль отключен или не
проверен;
- отсутствие DLP не является ошибкой Workforce core.
## Сетевое состояние
Доступ к DetMir зависит от NetworkManager VPN profile
`pfSense-gate-UDP4-1194-vpn_prog10-config`. Имя tun-интерфейса не является
семантической идентичностью и должно проверяться по адресу `10.0.13.*`.
Пример рабочего route:
```text
10.10.10.2 via 10.0.13.1 dev <current-detmir-tun>
```
Наблюдавшаяся нестабильность dataplane:
- tun device может присутствовать в routing table, но gateway `10.0.13.1` не
отвечает;
- при этом SSH, `/healthz` и браузерная проверка DetMir недоступны;
- после ручного поднятия NetworkManager-подключения
`pfSense-gate-UDP4-1194-vpn_prog10-config` доступ восстанавливался.
- 2026-06-25 после phase 1 deploy зафиксирован отдельный сбой:
`nm-openvpn` для `178.178.98.83:1194` получил `TLS handshake failed` и
`connect timeout exceeded`; повторная production-очистка старого systemd
prewarm drop-in отложена до восстановления VPN handshake.
Актуальная проверка 2026-06-29:
```text
192.168.100.19 via 10.0.13.1 dev <current-detmir-tun>
10.0.13.1 ping OK
192.168.100.19:5985 OK
192.168.100.19:3389 OK
```
Команда восстановления:
```bash
nmcli connection down 'pfSense-gate-UDP4-1194-vpn_prog10-config' || true
sleep 2
nmcli connection up 'pfSense-gate-UDP4-1194-vpn_prog10-config'
```
Проверка:
```bash
ping -c 2 -W 2 10.0.13.1
ping -c 2 -W 2 10.10.10.2
nc -vz -w 3 10.10.10.2 22
curl -sS --max-time 5 http://10.10.10.2:8720/healthz
```
## Что не менять без отдельной задачи
- Не удалять DLP collectors и warehouse ради ускорения портала.
- Не включать heavy DLP или Velociraptor server runtime автоматически при
обычном deploy без ресурсного решения.
- Не менять UI/API несовместимо: новые поля должны быть additive.
- Не заявлять completed DLP decoupling до live deploy и browser/API smoke.
- Не позиционировать AWatch-rus как сертифицированную DLP/SIEM/EDR/СЗИ.
## Следующий инженерный шаг
Закрыть оставшиеся production-hardening пункты:
1. После восстановления DetMir VPN повторно прогнать
`ansible/deploy_detmir_portal.yml`, чтобы удалить legacy drop-in
`/etc/systemd/system/detmir-portal.service.d/30-prewarm-after-start.conf`.
2. Подтвердить remote `sha256sum /usr/local/bin/detmir-portal` и отсутствие
`ExecStartPost` prewarm в `systemctl cat detmir-portal`.
3. Подтвердить browser smoke без зависания первичной загрузки.
4. Добавить метрики и smoke для режимов `dlp.enabled=false` и
`dlp.enabled=true`.
@@ -0,0 +1,83 @@
# DetMir restore baseline 2026-06-29
Дата фиксации: 2026-06-29.
Документ фиксирует фактическое состояние восстановленного контура
AWatch-rus/DetMir после возврата RDP-сервера `SHARKON2025`.
## Итог
- AW server: `10.10.10.13:5600`, API отвечает, CORS OK.
- Worktime API: `10.10.10.13:5610`, `/health` OK, report endpoints отвечают.
- Portal/gateway: `https://dm.iri1968.dpdns.org/healthz` отвечает `200 ok`.
- RDP host physical IP: `192.168.100.19`.
- Stable ActivityWatch logical host id: `SHARKON2025`.
- Старый IP `192.168.100.18` не считать текущим production target.
## Выполненные исправления
- На ноутбуке добавлен постоянный маршрут к `192.168.100.19/32` через DetMir
VPN gateway `10.0.13.1`.
- `ansible/inventory.ini` переведен на `rdp-prod ansible_host=192.168.100.19`.
- Диагностические скрипты больше не жёстко привязаны к `192.168.100.18`.
- `/etc/activitywatch/aw-server.env` на AW server обновлен:
`AW_MONITORED_WINDOWS_HOST=192.168.100.19`.
- На RDP host добавлены узкие Windows Firewall allow-правила для
`10.10.10.13 -> 5985/3389`; промежуточный firewall всё ещё блокирует этот
server-side TCP path.
- Guard restart-budget quarantine очищен через backup и reset runtime state.
- ClickHouse Security Finding Inbox schema применена:
`security_findings`, `security_finding_workflow_events`,
`security_finding_inbox`.
- `check-aw-full` обновлен: env-aware RDP host, корректный CORS origin,
AFK freshness через `bucket.metadata.end`.
- DLP runtime зафиксирован как optional disabled contour: отключён для снижения
нагрузки на Proxmox/InfluxDB/Grafana/ClickHouse/AW server, но оставлен
подключаемым через documented enable flow.
- Hayabusa/Velociraptor зафиксированы как optional security findings /
forensics layer, отдельный от DLP runtime и Workforce hot path.
## Проверенный статус по 7 пунктам
1. AW-server/portal/API: доступны; `check-aw-data.sh` OK, public `/healthz` OK.
2. RDP host: `192.168.100.19`, `COMPUTERNAME=SHARKON2025`,
WinRM/RDP/SSH доступны с админского ноутбука; guard service running.
3. ActivityWatch buckets: worktime, session, AFK, DLP endpoint signals fresh;
window bucket корректно классифицируется как inactive при отсутствии
интерактивной активности.
4. ClickHouse/filter/security findings: ClickHouse healthy, Security Finding
Inbox schema создана; portal endpoint защищен auth и без авторизации
возвращает `401`.
5. InfluxDB/Grafana: InfluxDB health `pass`, Grafana DB health `ok`,
Influx datasource health OK. Loki log contour intentionally disabled to
reduce resource usage.
6. Hayabusa/Velociraptor layer: Hayabusa doctor OK, drop.path active,
incoming/drop backlog empty, latest intake `2026-06-29T09:00:23Z`,
bad zip сохранен только в quarantine как evidence. Velociraptor integration
is treated as optional findings source; no always-on Velociraptor runtime is
required for Workforce/AW core.
7. Baseline зафиксирован в этом документе и связан с operational docs/skills.
## Остаточные риски
- `aw-rus-healthd.service` на AW server всё ещё видит TCP timeout до
`192.168.100.19:5985/3389` через gateway `10.10.10.1`, хотя ICMP проходит и
доступ с админского ноутбука есть. Это network policy gap на промежуточном
firewall/ACL, не SQLite/datastore failure.
- Grafana Loki datasource `10.10.10.12:3100` intentionally disabled: Proxmox
LXC `202 loki-logs` is stopped, active config has `onboot: 0`, and TCP
`10.10.10.12:3100` is closed. This is an operator decision to save resources
and does not break core AW/worktime/ClickHouse.
- DLP runtime is intentionally disabled for the current production profile and
must not be auto-enabled by routine deploys. Re-enable only after resource
budget check and explicit operator decision.
## Проверки
- `AW_MONITORED_WINDOWS_HOSTNAME=SHARKON2025 ./check-aw-data.sh` - OK.
- `AW_SMOKE_AW_SERVER=http://10.10.10.13:5600
AW_SMOKE_SOURCE_HOSTNAME=SHARKON2025
AW_SMOKE_WINDOWS_HOST=192.168.100.19 ./check-aw-full.sh --no-color` - OK.
- `cargo test -p check-aw-full` - 4 passed.
- `bash -n` для изменённых shell scripts - OK.
- `AW_SMOKE_LOKI_ENABLED=0` is the current default for local smoke checks.
+2 -1
View File
@@ -55,7 +55,7 @@ DETMIR_SUPPORT_PVE_HOST=10.10.10.2
DETMIR_SUPPORT_AW_HOST=10.10.10.13 DETMIR_SUPPORT_AW_HOST=10.10.10.13
DETMIR_SUPPORT_WEB_HOST=10.10.10.2 DETMIR_SUPPORT_WEB_HOST=10.10.10.2
DETMIR_SUPPORT_WEB_TLS_HOST=10.10.10.2 DETMIR_SUPPORT_WEB_TLS_HOST=10.10.10.2
DETMIR_SUPPORT_WINDOWS_HOST=192.168.100.18 DETMIR_SUPPORT_WINDOWS_HOST=192.168.100.19
DETMIR_SUPPORT_SURICATA_HOST=10.10.10.2 DETMIR_SUPPORT_SURICATA_HOST=10.10.10.2
DETMIR_SUPPORT_SSH_USER=igor DETMIR_SUPPORT_SSH_USER=igor
@@ -79,6 +79,7 @@ DETMIR_SUPPORT_PVE_BACKUP_DIRS=/var/lib/pve/local-btrfs/dump
- операторский gateway находится на `10.10.10.2`, а не на историческом - операторский gateway находится на `10.10.10.2`, а не на историческом
`10.10.10.11`; `10.10.10.11`;
- текущий Windows/RDP target после восстановления: `192.168.100.19`;
- web health endpoint: `https://10.10.10.2/healthz`, ожидаемый ответ `200`; - web health endpoint: `https://10.10.10.2/healthz`, ожидаемый ответ `200`;
- защищенный корень gateway `https://10.10.10.2/` штатно отвечает `401`; - защищенный корень gateway `https://10.10.10.2/` штатно отвечает `401`;
- AW API health проверяется через - AW API health проверяется через
+200
View File
@@ -0,0 +1,200 @@
# Optional DLP runtime for DetMir
Цель: DLP-контур должен отключаться управляемо, без ложных аварий в health/readiness и без автоматического подъема heavy-пайплайна, когда задача контура - снизить нагрузку на InfluxDB, Grafana и ClickHouse.
## Что отключается
Штатный runtime off включает:
- `AW_DLP_ENABLED=false` на AW server;
- `DETMIR_DLP_ENABLED=false` в управляющем DetMir contour check;
- `DETMIR_PORTAL_DLP_MODULE_ENABLED=false` для portal UI/API DLP-модуля;
- остановку DLP timers/services:
- `aw-dlp-influx-exporter.timer`;
- `activitywatch-dlp-aggregator.timer`;
- `aw-dlp-report-scheduler.timer`;
- `aw-dlp-syslog-forwarder.timer`;
- `aw-dlp-webhook-sender.timer`;
- `aw-dlp-cef-exporter.timer`;
- `aw-dlp-ioc-refresh.timer`;
- `aw-dlp-policy-engine.service`;
- `aw-dlp-case-management.service`;
- `detmir-portal-evidence.service`, если DLP evidence upload больше не нужен.
Worktime, ActivityWatch server, browser/window/AFK collection, Hayabusa,
Velociraptor findings ingest и 1C/ClickHouse core не считаются DLP runtime и
отдельно не отключаются.
Важно: отключение DLP runtime не удаляет DLP-контур из проекта. Это
эксплуатационный режим `disabled`, выбранный для production DetMir из-за
существенной нагрузки на виртуальную среду Proxmox, InfluxDB, Grafana,
ClickHouse и AW server. DLP должен оставаться подключаемым обратно через
описанный ниже enable-процесс, без переустановки продукта и без потери
исторических артефактов до отдельной retention/cleanup процедуры.
## Статистика перед отключением
На AW server:
```bash
sudo bash /usr/local/bin/detmir-dlp-runtime-control stats
sudo cat /var/lib/activitywatch/health/dlp-runtime-state.json
```
Из репозитория до деплоя:
```bash
sudo bash scripts/detmir_dlp_runtime_control.sh stats
```
JSON фиксирует:
- generated timestamp;
- effective mode;
- DLP unit `active/enabled/load` state;
- последние timestamps по DLP buckets для текущего host;
- причину отключения.
Каждый запуск дополнительно сохраняет неизменяемый снимок в
`/var/lib/activitywatch/health/dlp-runtime-history/`. При `disable` остаются
как минимум два снимка: `pre_disable` до остановки units и `disabled` после
остановки/disable/reset-failed.
## Отключение
На AW server:
```bash
sudo install -o root -g root -m 0755 scripts/detmir_dlp_runtime_control.sh /usr/local/bin/detmir-dlp-runtime-control
sudo sed -i \
-e 's/^AW_DLP_ENABLED=.*/AW_DLP_ENABLED=false/' \
-e 's/^AW_DLP_DISABLED_REASON=.*/AW_DLP_DISABLED_REASON=operator_disabled_to_reduce_influx_grafana_clickhouse_load/' \
/etc/activitywatch/aw-server.env
sudo /usr/local/bin/detmir-dlp-runtime-control disable
sudo systemctl restart aw-worktime-api.service || true
```
Для portal:
```bash
sudo sed -i 's/^DETMIR_PORTAL_DLP_MODULE_ENABLED=.*/DETMIR_PORTAL_DLP_MODULE_ENABLED=false/' /etc/detmir-portal.env
sudo systemctl restart detmir-portal.service
```
Если переменной нет, добавьте ее в соответствующий env-файл отдельной строкой.
## Проверка после отключения
```bash
AW_DLP_ENABLED=false DETMIR_DLP_ENABLED=false /usr/local/bin/dlp-health-check --json
DETMIR_DLP_ENABLED=false detmir-dlp
DETMIR_DLP_ENABLED=false detmir-check --json
AW_DLP_ENABLED=false check-aw-full
```
Ожидаемое поведение:
- `dlp-health-check` возвращает `ok=true` и `dlp:mode=disabled`;
- `detmir-dlp` не открывает SSH health probe и возвращает disabled payload;
- `detmir-check` не проверяет DLP buckets;
- `check-aw-full` показывает DLP buckets как `SKIPPED`;
- DLP units остаются stopped/disabled;
- worktime/core проверки продолжают работать.
## Live evidence 2026-06-25
На DetMir/AW server выполнен controlled disable:
- `AW_DLP_ENABLED=false`;
- `AW_DLP_INFLUX_ENABLED=false`;
- `AW_DLP_DISABLED_REASON=operator_disabled_to_reduce_influx_grafana_clickhouse_load`;
- `AW_DLP_DISABLED_SINCE=2026-06-25`.
Зафиксированы evidence-снимки:
```text
/var/lib/activitywatch/health/dlp-runtime-history/dlp-runtime-current-20260625T083619Z.json
/var/lib/activitywatch/health/dlp-runtime-history/dlp-runtime-pre_disable-20260625T083637Z.json
/var/lib/activitywatch/health/dlp-runtime-history/dlp-runtime-disabled-20260625T083700Z.json
```
Результат:
- pre-disable active units included DLP Influx exporter, aggregator, report,
integration, policy/case and evidence units;
- post-disable active/enabled DLP units: `0/0`;
- `dlp-health-check` returned `ok=true`, `dlp:mode=disabled`;
- `detmir-dlp` returned `ok=true`, `dlp:mode=disabled`;
- `check-aw-full` returned `DLP buckets ... SKIPPED`.
Отдельные non-DLP findings того же ручного прогона, до восстановления
`192.168.100.19`:
- AFK/window/worktime buckets were stale and require RDP collector/session
recovery;
- WinRM from server side to `192.168.100.18:5985` was unreachable;
- these findings are outside the DLP runtime disable boundary.
## Возврат DLP
```bash
sudo sed -i 's/^AW_DLP_ENABLED=.*/AW_DLP_ENABLED=true/' /etc/activitywatch/aw-server.env
sudo /usr/local/bin/detmir-dlp-runtime-control enable
sudo systemctl restart aw-worktime-api.service || true
```
Для portal:
```bash
sudo sed -i 's/^DETMIR_PORTAL_DLP_MODULE_ENABLED=.*/DETMIR_PORTAL_DLP_MODULE_ENABLED=true/' /etc/detmir-portal.env
sudo systemctl restart detmir-portal.service
```
После включения выполнить:
```bash
/usr/local/bin/dlp-health-check --json
detmir-dlp
detmir-check --json
```
Перед возвратом DLP в production обязательно проверить ресурсный бюджет
Proxmox/InfluxDB/Grafana/ClickHouse. Не включайте DLP timers/services
автоматически вместе с обычным deploy, если текущая цель - сохранить лёгкий
Workforce/AW контур.
## Hayabusa/Velociraptor при выключенном DLP
Hayabusa/Sigma и Velociraptor относятся к optional security findings /
forensics layer, а не к тяжёлому DLP runtime:
- Hayabusa drop/autoprocess может продолжать работать при выключенном DLP;
- Velociraptor findings ingest может использоваться в offline/server mode, если
администратор явно включил соответствующий режим;
- результаты должны попадать в Security Finding Inbox / ClickHouse как
контролируемые findings, а не запускать автоматическую блокировку без
approve/apply workflow;
- portal/workforce first screen не должен ждать Velociraptor или DLP;
- disabled DLP mode не должен превращаться в FAIL только из-за отсутствия DLP
buckets.
Velociraptor server/client runtime не должен стартовать автоматически в
production DetMir без отдельного ресурсного решения. Для малой виртуальной
среды предпочтителен `disabled` или `offline_collector` режим.
## Ansible
В inventory/group vars:
```yaml
aw_dlp_enabled: false
aw_dlp_disabled_reason: "operator_disabled_to_reduce_influx_grafana_clickhouse_load"
aw_dlp_disabled_since: "2026-06-25"
detmir_portal_dlp_module_enabled_override: false
```
При `aw_dlp_enabled: false` playbook пишет `AW_DLP_ENABLED=false`, не включает DLP service/timer runtime и не должен возвращать DLP Influx exporter/aggregator в active state.
## Ограничения
Это не удаление DLP-функциональности и не заявление, что DLP заменен другим средством. Это штатный режим временного/постоянного отключения тяжелого DLP runtime для стабилизации производительности. Исторические DLP buckets и артефакты могут оставаться на диске и в ActivityWatch до отдельной retention/cleanup процедуры.
+17 -2
View File
@@ -94,11 +94,26 @@ Browser smoke не заменяет API/CLI проверки. Он подтве
```bash ```bash
cd /mnt/usb_hdd2/Projects/ActivityWatch-Russian cd /mnt/usb_hdd2/Projects/ActivityWatch-Russian
NO_PROXY=localhost,127.0.0.1,10.10.10.13,10.10.10.2,192.168.100.18,10.10.10.0/24 \ NO_PROXY=localhost,127.0.0.1,10.10.10.13,10.10.10.2,192.168.100.19,10.10.10.0/24 \
no_proxy=localhost,127.0.0.1,10.10.10.13,10.10.10.2,192.168.100.18,10.10.10.0/24 \ no_proxy=localhost,127.0.0.1,10.10.10.13,10.10.10.2,192.168.100.19,10.10.10.0/24 \
AW_SMOKE_WINDOWS_HOST=192.168.100.19 \
AW_SMOKE_SOURCE_HOSTNAME=SHARKON2025 \
./check-aw-full.sh ./check-aw-full.sh
``` ```
Для DetMir production при проверке после rename RDP-сервера явно фиксируйте
stable logical host id:
```bash
AW_MONITORED_WINDOWS_HOSTNAME=SHARKON2025 ./check-aw-data.sh
AW_SMOKE_WINDOWS_HOST=192.168.100.19 \
AW_SMOKE_SOURCE_HOSTNAME=SHARKON2025 \
./scripts/aw-contour-smoke-local.sh --skip-winrm
```
`SHARKON2025` в этих командах - исторический ActivityWatch logical id, не
физическое имя Windows-сервера.
Для workforce ClickHouse дополнительно проверить quality views: Для workforce ClickHouse дополнительно проверить quality views:
```sql ```sql
+84 -5
View File
@@ -53,9 +53,77 @@ backup, registry-readiness документации, плана российск
(`docs/PR_REVIEW_EVIDENCE_RU.md`). (`docs/PR_REVIEW_EVIDENCE_RU.md`).
- Branch protection evidence package: ready - Branch protection evidence package: ready
(`docs/BRANCH_PROTECTION_EVIDENCE_RU.md`). (`docs/BRANCH_PROTECTION_EVIDENCE_RU.md`).
- Branch protection actual enablement: - Branch protection ruleset: `verified_active_ruleset`.
`pending_manual_verification`. - Branch protection target branch: `main`.
- Branch protection required checks: `Coverage baseline`, `security`,
`rust-checks`, `docs-registry-checks`, `smoke-checks`.
- First protected PR workflow: PR #50 opened; required checks passed;
review/merge still `pending_review_required`.
- First reviewed PR evidence: pending. - First reviewed PR evidence: pending.
- DetMir portal live baseline on 2026-06-25:
`docs/DETMIR_CURRENT_STATE_RU.md`.
- DetMir portal deployed binary:
`653b22b0fbf29a22f7de42ade7b689490b1de16fa07e785e4e0efd3078e7a3bc`.
- DetMir portal cold-start UI hang: mitigated. During cold/prewarm state the UI
now shows `STALE / Первичный срез прогревается`, not endless loading.
- DetMir DLP hot-path boundary: phase 1 deployed on the portal service with
`DETMIR_PORTAL_DLP_MODULE_ENABLED=false`.
- DetMir optional DLP runtime controls: implemented in code/docs through
`AW_DLP_ENABLED`, `DETMIR_DLP_ENABLED`,
`scripts/detmir_dlp_runtime_control.sh` and
`docs/DLP_OPTIONAL_RUNTIME_RU.md`.
- DetMir optional DLP runtime live state: disabled on 2026-06-25 to reduce
InfluxDB/Grafana/ClickHouse/AW server load. Evidence:
`dlp-health-check=dlp:mode disabled`, `detmir-dlp=dlp:mode disabled`,
active/enabled DLP units `0/0`, history snapshots under
`/var/lib/activitywatch/health/dlp-runtime-history/`.
- DetMir DLP contour status: disabled for the current production resource
profile, not removed. It remains a documented optional module and must only be
re-enabled after explicit operator decision and Proxmox/InfluxDB/Grafana/
ClickHouse capacity check.
- DetMir DLP buckets in manual full check: `SKIPPED` under
`AW_DLP_ENABLED=false`, not reported as dead.
- DetMir RDP collector freshness after 2026-06-29 restore: physical RDP target
is `192.168.100.19`, stable AW logical host id remains `SHARKON2025`.
Buckets are fresh/inactive as expected, collector guard quarantine was reset,
and `check-aw-full` was updated to use env-driven RDP target and AFK
`metadata.end` freshness. Admin laptop route to `192.168.100.19` goes through
DetMir OpenVPN gateway `10.0.13.1`; WinRM `5985` and RDP `3389` are reachable
from the admin laptop.
- Low-cost containment pack: first safe control-plane layer implemented as
Rust `containment-engine`, example policy/finding fixtures, Ansible/env
disabled-by-default configuration, and operator/policy runbooks. Current
engine is decision/shadow only and does not mutate firewall, pfSense, AD,
VLAN or routes.
- Security Finding Inbox: ClickHouse schema, DetMir Portal view/API,
Hayabusa/Velociraptor ingest adapters and separate
`security-finding-inbox executor` are implemented. Portal records workflow
events only; approved `apply_requested` can be processed by the fail-closed
executor through `containment-engine` `decide -> plan -> apply -> verify`
with rollback on apply failure.
- Velociraptor/Hayabusa status: implemented as optional findings/forensics
sources. They are not required for Workforce hot path, do not replace DLP or
SIEM, and must not start heavy always-on runtime in DetMir production unless
`offline_collector` or `server_clients` mode is explicitly selected.
- Security Finding Inbox live schema: applied on ClickHouse 2026-06-29
(`security_findings`, `security_finding_workflow_events`,
`security_finding_inbox`).
- DetMir Loki log contour: intentionally disabled by operator to reduce
resource usage. Proxmox LXC `202 loki-logs` is stopped, active config has
`onboot: 0`, and smoke checks skip Loki by default unless
`AW_SMOKE_LOKI_ENABLED=1` is set.
- DetMir restore baseline 2026-06-29:
`docs/DETMIR_RESTORE_BASELINE_2026-06-29_RU.md`.
- DetMir API smoke after phase 1: `/healthz` and `/readyz` OK;
`/api/reports` returned `cache_status=warming` with
`modules.dlp.enabled=false`; `/api/operator` returned bounded
`cache_status=warming` / `summary.severity=STALE` without waiting for the
full cold snapshot.
- DetMir remaining heavy path risk: full report/snapshot prewarm can still be
CPU/IO expensive; deeper optimization remains pending.
- DetMir VPN access rule: do not identify the contour by `tun0`/`tun1`; verify
by NetworkManager profile, `10.0.13.*` address, route via `10.0.13.1` and live
reachability.
## Что готово ## Что готово
@@ -92,10 +160,17 @@ backup, registry-readiness документации, плана российск
`docs/BRANCH_PROTECTION_POLICY_RU.md`. `docs/BRANCH_PROTECTION_POLICY_RU.md`.
- Branch protection evidence template prepared: - Branch protection evidence template prepared:
`docs/BRANCH_PROTECTION_EVIDENCE_RU.md`. `docs/BRANCH_PROTECTION_EVIDENCE_RU.md`.
- GitHub ruleset / branch protection for `main` verified active by maintainer:
ruleset `main`, target branch `main`, empty bypass list, required PR with
one approval, stale approval dismissal, Code Owners review, required status
checks and force-push blocking.
- PR-based review workflow documented: - PR-based review workflow documented:
`docs/PR_REVIEW_WORKFLOW_RU.md`. `docs/PR_REVIEW_WORKFLOW_RU.md`.
- PR review evidence template prepared: - PR review evidence template prepared:
`docs/PR_REVIEW_EVIDENCE_RU.md`. `docs/PR_REVIEW_EVIDENCE_RU.md`.
- First protected PR workflow evidence recorded for PR #50:
required checks passed; review requirement is still pending; merge status is
open; no admin bypass recorded.
- Зафиксирован residual risk register: - Зафиксирован residual risk register:
`docs/RESIDUAL_RISKS_RU.md`. `docs/RESIDUAL_RISKS_RU.md`.
- Подготовлен план публичных issues для ручного заведения: - Подготовлен план публичных issues для ручного заведения:
@@ -117,13 +192,16 @@ backup, registry-readiness документации, плана российск
- Финальная юридическая проверка пакета документов перед подачей. - Финальная юридическая проверка пакета документов перед подачей.
- Проверка совместимости с российскими ОС. - Проверка совместимости с российскими ОС.
- Visible external code review is still pending. - Visible external code review is still pending.
- Branch protection enablement is not claimed until repository settings are
verified.
- Branch protection actual enablement remains `pending_manual_verification`.
- First reviewed PR evidence remains pending until a reviewed public PR is - First reviewed PR evidence remains pending until a reviewed public PR is
merged and evidence is recorded. merged and evidence is recorded.
- External peer review remains pending.
- Community adoption remains low until external contributors, public reviews - Community adoption remains low until external contributors, public reviews
and sustained third-party activity appear. and sustained third-party activity appear.
- DetMir DLP runtime disable is complete for the current live contour; deeper
long-term DLP product modularization and retention/cleanup policy remain
separate future work.
- DetMir RDP collector/session recovery after 2026-06-29 restore is verified by
live smoke: `check-aw-full` reports `FRESH=8 STALE=0 DEAD=0`.
## Честные ограничения ## Честные ограничения
@@ -161,3 +239,4 @@ backup, registry-readiness документации, плана российск
- `docs/public-issues/public-issues-manifest.json` - `docs/public-issues/public-issues-manifest.json`
- `docs/BRANCH_PROTECTION_POLICY_RU.md` - `docs/BRANCH_PROTECTION_POLICY_RU.md`
- `docs/BRANCH_PROTECTION_EVIDENCE_RU.md` - `docs/BRANCH_PROTECTION_EVIDENCE_RU.md`
- `docs/DETMIR_CURRENT_STATE_RU.md`
+20 -10
View File
@@ -1,13 +1,14 @@
# AWatch-rus: PR review evidence # AWatch-rus: PR review evidence
Дата: 2026-06-23 Дата: 2026-06-24
pr_review_evidence_status: "pending_until_first_reviewed_pr_is_merged" pr_review_status: "pending_review_required"
GitHub issue: https://github.com/igor04091968/AWatch-rus/issues/48 GitHub issue: https://github.com/igor04091968/AWatch-rus/issues/48
Этот документ фиксирует, что будет считаться evidence для PR-based review Этот документ фиксирует evidence первого protected PR-based workflow после
workflow. Он не утверждает, что external peer review уже выполнен. включения GitHub ruleset на `main`. Он не утверждает, что review requirement
уже выполнен или что external peer review завершен.
## Evidence Criteria ## Evidence Criteria
@@ -23,16 +24,23 @@ workflow. Он не утверждает, что external peer review уже в
## Evidence Record ## Evidence Record
- PR URL: `pending` - PR URL: `https://github.com/igor04091968/AWatch-rus/pull/50`
- Linked issue URL: `pending` - Linked issue URL: `https://github.com/igor04091968/AWatch-rus/issues/48`
- Evidence issue URL: `https://github.com/igor04091968/AWatch-rus/issues/49`
- Required checks status: `passed`
- Required checks: `Coverage baseline`, `security`, `rust-checks`,
`docs-registry-checks`, `smoke-checks`
- Review requirement status: `pending_review_required`
- Merge status: `open`
- Admin bypass used: `false`
- Reviewer: `pending` - Reviewer: `pending`
- Reviewer type: `pending` - Reviewer type: `pending`
- Approval URL or screenshot filename: `pending` - Approval URL or screenshot filename: `pending`
- Passed checks: `pending`
- Merge commit: `pending` - Merge commit: `pending`
- Bypass used: `pending` - Date: `2026-06-24`
- Date: `YYYY-MM-DD` - Maintainer note: PR #50 demonstrates protected branch workflow and required
- Maintainer note: `pending` checks execution, but first reviewed PR evidence remains pending until review
requirement is satisfied and the PR is merged without bypass.
## Reviewer Interpretation ## Reviewer Interpretation
@@ -46,6 +54,8 @@ the review is visible.
- PR workflow documentation: ready. - PR workflow documentation: ready.
- PR template: ready. - PR template: ready.
- CODEOWNERS routing: ready. - CODEOWNERS routing: ready.
- First protected PR validation reference: PR #50.
- Required checks for PR #50: passed.
- First reviewed PR evidence: pending. - First reviewed PR evidence: pending.
- External peer review completed: not claimed. - External peer review completed: not claimed.
+17 -13
View File
@@ -50,26 +50,31 @@ visibility, release evidence contour and legal package. Они требуют д
## 2. Нет внешнего visible peer review / публично видимого peer review ## 2. Нет внешнего visible peer review / публично видимого peer review
- Текущий статус: риск открыт; PR template, CODEOWNERS, public governance - Текущий статус: риск открыт частично; PR template, CODEOWNERS, public
issues and PR workflow docs exist, but first reviewed PR evidence and branch governance issues, PR workflow docs and verified active GitHub ruleset for
protection verification are still pending. `main` exist. PR #50 has run through protected checks, but first reviewed PR
evidence is still pending because review/merge is not complete.
- Влияние: внешним аудиторам сложнее оценить review discipline and change - Влияние: внешним аудиторам сложнее оценить review discipline and change
control maturity. control maturity.
- Почему не блокирует pilot/readiness stage: current changes are protected by - Почему не блокирует pilot/readiness stage: current changes are protected by
reproducible checks, public workflows and documented conservative positioning. reproducible checks, public workflows and documented conservative positioning.
- Как риск будет снижаться: review checklist, CODEOWNERS routing, public issue - Как риск будет снижаться: review checklist, CODEOWNERS routing, public issue
#48, `docs/PR_REVIEW_WORKFLOW_RU.md`, first public reviewed PR evidence, #48, `docs/PR_REVIEW_WORKFLOW_RU.md`, first public reviewed PR evidence and
explicit release branch review policy and verified branch protection evidence. explicit release branch review policy.
- Уже снижающие evidence/documents/CI: `.github/pull_request_template.md`, - Уже снижающие evidence/documents/CI: `.github/pull_request_template.md`,
`.github/CODEOWNERS`, `.github/ISSUE_TEMPLATE/`, `.github/CODEOWNERS`, `.github/ISSUE_TEMPLATE/`,
`docs/REVIEW_CHECKLIST_RU.md`, `docs/BRANCH_PROTECTION_POLICY_RU.md`, `docs/REVIEW_CHECKLIST_RU.md`, `docs/BRANCH_PROTECTION_POLICY_RU.md`,
`docs/BRANCH_PROTECTION_EVIDENCE_RU.md`, `docs/BRANCH_PROTECTION_EVIDENCE_RU.md`,
`docs/PR_REVIEW_WORKFLOW_RU.md`, `docs/PR_REVIEW_EVIDENCE_RU.md`, `docs/PR_REVIEW_WORKFLOW_RU.md`, `docs/PR_REVIEW_EVIDENCE_RU.md`,
`CONTRIBUTING.md`, `SECURITY.md`, public CI, public security workflow. `CONTRIBUTING.md`, `SECURITY.md`, public CI, public security workflow.
- Residual risk: remains open until branch protection is verified and first - Branch protection pending risk: mitigated by verified active GitHub ruleset
reviewed PR evidence exists. for `main` with empty bypass list, required PR review, Code Owners review,
- Следующий action: выполнить maintainer verification for branch protection and required status checks and force-push blocking.
record first reviewed PR evidence without overstating external peer review. - PR workflow risk: remains open until PR #50 or a later PR is reviewed and
merged without bypass.
- Residual risk: remains open until first reviewed PR evidence exists.
- Следующий action: record first reviewed PR evidence without overstating
external peer review.
## 3. Низкая публичная активность issue tracker ## 3. Низкая публичная активность issue tracker
@@ -185,12 +190,11 @@ visibility, release evidence contour and legal package. Они требуют д
- PR review checklist exists in `docs/REVIEW_CHECKLIST_RU.md`. - PR review checklist exists in `docs/REVIEW_CHECKLIST_RU.md`.
- Advisory branch protection policy exists in - Advisory branch protection policy exists in
`docs/BRANCH_PROTECTION_POLICY_RU.md`. `docs/BRANCH_PROTECTION_POLICY_RU.md`.
- Branch protection evidence template exists in - Branch protection evidence exists in `docs/BRANCH_PROTECTION_EVIDENCE_RU.md`
`docs/BRANCH_PROTECTION_EVIDENCE_RU.md` with with `verified_active_ruleset` status.
`pending_manual_verification` status.
- PR-based workflow documentation exists in `docs/PR_REVIEW_WORKFLOW_RU.md`. - PR-based workflow documentation exists in `docs/PR_REVIEW_WORKFLOW_RU.md`.
- PR review evidence template exists in `docs/PR_REVIEW_EVIDENCE_RU.md`; - PR review evidence template exists in `docs/PR_REVIEW_EVIDENCE_RU.md`;
first reviewed PR evidence remains pending. PR #50 required checks passed, but first reviewed PR evidence remains pending.
- Public PR template includes security, registry-claim, runtime/API/UI, - Public PR template includes security, registry-claim, runtime/API/UI,
smoke-test, rollback and evidence checklist items. smoke-test, rollback and evidence checklist items.
- Visible external code review remains pending until public reviewed PRs exist. - Visible external code review remains pending until public reviewed PRs exist.
+133
View File
@@ -0,0 +1,133 @@
# Stable logical host id для Windows/RDP контура
Дата фиксации: 2026-06-27.
Обновление 2026-06-29: восстановленный production RDP host доступен как
`192.168.100.19`, но logical host id остаётся `SHARKON2025`.
Этот документ описывает правило, которое защищает AWatch-rus/DetMir от поломки
при переименовании Windows/RDP сервера.
## Правило
В AWatch-rus есть два разных идентификатора:
| Поле | Назначение | Можно менять при rename Windows |
| --- | --- | --- |
| physical Windows name / `COMPUTERNAME` | имя ОС, локальный домен учеток, WinRM/администрирование | да |
| `awHostname` / logical host id | суффикс ActivityWatch bucket, Grafana переменные, ClickHouse workforce keys, worktime reports | нет, только через плановую миграцию |
Для DetMir production текущий stable logical host id:
```text
SHARKON2025
```
Это legacy logical id для сохранения истории bucket-ов и дашбордов. Он больше
не должен трактоваться как обязательное физическое имя Windows-сервера.
Физический IP/адрес администрирования задаётся отдельно в inventory/env:
```text
rdp-prod ansible_host=192.168.100.19
AW_MONITORED_WINDOWS_HOST=192.168.100.19
```
## Где задается
Windows deploy:
```yaml
aw_windows_logical_host_id: "SHARKON2025"
aw_windows_hostname_override: "{{ aw_windows_logical_host_id }}"
```
Файл:
```text
ansible/host_vars/rdp-prod.yml
```
Server-side reports:
```text
AW_WORKTIME_HOST=<logical_host_id>
AW_MONITORED_WINDOWS_HOSTNAME=<logical_host_id>
AW_WORKTIME_INFLUX_HOSTS=<logical_host_id>
AW_DLP_INFLUX_HOSTS=<logical_host_id>
```
Windows runtime config:
```text
C:\ProgramData\AWatch-rus\deployment-config.json
```
Ключ:
```json
{
"awHostname": "SHARKON2025"
}
```
## Что делать при переименовании RDP сервера
1. Не менять `awHostname`, если нет отдельного плана миграции исторических
bucket-ов, Grafana и ClickHouse.
2. Windows account domain / local logon prefix можно задать явно:
```yaml
aw_windows_domain: "<new_windows_computer_or_domain_name>"
```
Если `aw_windows_domain` пустой или оставлен как `HOST-EXAMPLE`, playbook
прочитает текущий `$env:COMPUTERNAME` через WinRM и использует его только для
Windows-учёток. Это не меняет `awHostname`.
3. Повторно применить Windows deploy после восстановления WinRM:
```bash
cd ansible
ansible-playbook -i inventory.ini deploy_aw_windows.yml --limit rdp-prod
```
4. Проверить на Windows:
```powershell
Get-Content -Raw 'C:\ProgramData\AWatch-rus\deployment-config.json' |
ConvertFrom-Json |
Select-Object awHostname,userTasks
```
5. Проверить ActivityWatch buckets:
```bash
AW_MONITORED_WINDOWS_HOSTNAME=SHARKON2025 ./check-aw-data.sh
```
## Что ломается, если использовать `COMPUTERNAME` как bucket id
- появляются новые пустые bucket-и после rename;
- старые dashboards продолжают смотреть на старый host;
- `aw-worktime-api` считает источники stale/missing;
- ClickHouse workforce catalog перестает связывать пользователей с событиями;
- guard/recovery может искать неправильные launch tasks.
## Миграция на новый logical id
Переход с `SHARKON2025` на нейтральный id вроде `DETMIR-RDP-01` допустим только
как отдельная planned migration:
- остановить Windows collectors;
- создать mapping старого и нового logical id;
- обновить Grafana dashboards;
- обновить ClickHouse workforce catalog;
- решить, переносить ли исторические ActivityWatch bucket-и или оставить их
read-only;
- обновить `AW_WORKTIME_HOST`, `AW_MONITORED_WINDOWS_HOSTNAME`,
`AW_WORKTIME_INFLUX_HOSTS`, `AW_DLP_INFLUX_HOSTS`;
- перезапустить server-side services;
- выполнить smoke-check и ручную проверку портала.
Без этих шагов менять logical id в production нельзя.
@@ -113,7 +113,9 @@
"github_issue_url": "https://github.com/igor04091968/AWatch-rus/issues/48", "github_issue_url": "https://github.com/igor04091968/AWatch-rus/issues/48",
"created_at": "2026-06-23T19:43:08Z", "created_at": "2026-06-23T19:43:08Z",
"created_by": "maintainer", "created_by": "maintainer",
"next_evidence_doc": "docs/PR_REVIEW_EVIDENCE_RU.md" "next_evidence_doc": "docs/PR_REVIEW_EVIDENCE_RU.md",
"evidence_status": "pending_review_required",
"evidence_doc": "docs/PR_REVIEW_EVIDENCE_RU.md"
}, },
{ {
"id": "012", "id": "012",
@@ -124,7 +126,9 @@
"github_issue_url": "https://github.com/igor04091968/AWatch-rus/issues/49", "github_issue_url": "https://github.com/igor04091968/AWatch-rus/issues/49",
"created_at": "2026-06-23T19:43:10Z", "created_at": "2026-06-23T19:43:10Z",
"created_by": "maintainer", "created_by": "maintainer",
"next_evidence_doc": "docs/BRANCH_PROTECTION_EVIDENCE_RU.md" "next_evidence_doc": "docs/BRANCH_PROTECTION_EVIDENCE_RU.md",
"evidence_status": "verified_active_ruleset",
"evidence_doc": "docs/BRANCH_PROTECTION_EVIDENCE_RU.md"
} }
] ]
} }
@@ -1,5 +1,66 @@
# Registry readiness changelog # Registry readiness changelog
## 2026-06-24 protected PR workflow evidence recorded
Changed:
- Recorded PR #50 as the first protected PR workflow reference in
`docs/PR_REVIEW_EVIDENCE_RU.md`.
- Recorded that required ruleset checks passed for PR #50: `Coverage baseline`,
`security`, `rust-checks`, `docs-registry-checks`, `smoke-checks`.
- Recorded that PR #50 remains `pending_review_required`; first reviewed PR
evidence is not claimed complete.
- Linked issue #48 in `docs/public-issues/public-issues-manifest.json` to
`docs/PR_REVIEW_EVIDENCE_RU.md` with `pending_review_required`.
Runtime impact:
- No runtime/product code changes.
- No API changes.
- No UI changes.
- No business logic changes.
Guardrails:
- GitHub remains public mirror validation only.
- Protected PR workflow evidence is governance evidence only.
- This is not registry release evidence.
- External peer review is not claimed completed.
- Russian Gitea plus planned Russian build-runner remains the primary registry
contour.
## 2026-06-23 verified GitHub ruleset evidence
Changed:
- Recorded maintainer-verified active GitHub ruleset `main` for target branch
`main` in `docs/BRANCH_PROTECTION_EVIDENCE_RU.md`.
- Recorded empty bypass list, required pull request review, one required
approval, stale approval dismissal and Code Owners review.
- Recorded required status checks: `Coverage baseline`, `security`,
`rust-checks`, `docs-registry-checks`, `smoke-checks`.
- Linked issue #49 in `docs/public-issues/public-issues-manifest.json` to
verified ruleset evidence.
- Updated project status, branch protection policy and residual risks to show
branch protection risk mitigated while first reviewed PR evidence remains
pending.
Runtime impact:
- No runtime/product code changes.
- No API changes.
- No UI changes.
- No business logic changes.
Guardrails:
- GitHub remains public mirror validation only.
- Verified GitHub ruleset is public governance evidence only.
- This is not registry release evidence.
- Russian Gitea plus planned Russian build-runner remains the primary registry
contour.
- External peer review is not claimed completed.
## 2026-06-23 branch protection and PR review evidence package ## 2026-06-23 branch protection and PR review evidence package
Added: Added:
+25 -10
View File
@@ -23,9 +23,14 @@ PROXMOX_HOST="10.10.10.2"
AW_HOST="10.10.10.13" AW_HOST="10.10.10.13"
GRAFANA_HOST="10.10.10.11" GRAFANA_HOST="10.10.10.11"
INFLUXDB_HOST="10.10.10.10" INFLUXDB_HOST="10.10.10.10"
WINDOWS_HOST="192.168.100.18" WINDOWS_HOST="${AW_WINDOWS_HOST:-${AW_SMOKE_WINDOWS_HOST:-192.168.100.19}}"
CLICKHOUSE_HOST="10.10.10.2" CLICKHOUSE_HOST="10.10.10.2"
SOURCE_HOSTNAME="SHARKON2025" SOURCE_HOSTNAME="${AW_LOGICAL_HOST_ID:-${AW_MONITORED_WINDOWS_HOSTNAME:-${AW_SMOKE_SOURCE_HOSTNAME:-SHARKON2025}}}"
DLP_ENABLED="${AW_DLP_ENABLED:-${DETMIR_DLP_ENABLED:-true}}"
case "${DLP_ENABLED,,}" in
0|false|no|off) DLP_ENABLED=false ;;
*) DLP_ENABLED=true ;;
esac
QUICK_MODE=0 QUICK_MODE=0
SKIP_WINDOWS=0 SKIP_WINDOWS=0
@@ -102,7 +107,7 @@ check_http_json_key() {
check_bucket_freshness() { check_bucket_freshness() {
local bucket="$1" label="$2" remediation="$3" local bucket="$1" label="$2" remediation="$3"
local bucket_id="${bucket}_${SOURCE_HOSTNAME}" local bucket_id="${bucket}_${SOURCE_HOSTNAME}"
local tmp last_ts event_epoch now age_sec local tmp last_ts event_epoch now age_sec meta_ts
tmp="$(mktemp)" tmp="$(mktemp)"
if ! curl -fsS --connect-timeout 5 --max-time 15 "$AW_SERVER/api/0/buckets/$bucket_id/events?limit=1" -o "$tmp" 2>"$tmp.err"; then if ! curl -fsS --connect-timeout 5 --max-time 15 "$AW_SERVER/api/0/buckets/$bucket_id/events?limit=1" -o "$tmp" 2>"$tmp.err"; then
fail "bucket $label ($bucket_id) — запрос не удался" fail "bucket $label ($bucket_id) — запрос не удался"
@@ -113,6 +118,12 @@ check_bucket_freshness() {
fi fi
last_ts="$(jq -r '.[0].timestamp // empty' "$tmp" 2>/dev/null)" last_ts="$(jq -r '.[0].timestamp // empty' "$tmp" 2>/dev/null)"
rm -f "$tmp" rm -f "$tmp"
if [ "$bucket" = "aw-watcher-afk" ]; then
meta_ts="$(curl -fsS --connect-timeout 5 --max-time 15 "$AW_SERVER/api/0/buckets/$bucket_id" 2>/dev/null | jq -r '.metadata.end // empty' 2>/dev/null || true)"
if [ -n "$meta_ts" ]; then
last_ts="$meta_ts"
fi
fi
if [ -z "$last_ts" ]; then if [ -z "$last_ts" ]; then
warn "bucket $label ($bucket_id) — нет событий" warn "bucket $label ($bucket_id) — нет событий"
echo " REMEDIATION: $remediation" echo " REMEDIATION: $remediation"
@@ -268,11 +279,11 @@ if [ "$QUICK_MODE" = "1" ]; then
section "4. Buckets (быстрый режим)" section "4. Buckets (быстрый режим)"
for entry in \ for entry in \
"aw-watcher-afk|AFK watcher|Запустите на RDP: schtasks /Run /TN \"ActivityWatch Recovery\" или schtasks /Run /TN \"ActivityWatch Launch [SHARKON2025_Администратор]\"" \ "aw-watcher-afk|AFK watcher|Запустите на RDP: schtasks /Run /TN \"ActivityWatch Recovery\" или launch task для logical host $SOURCE_HOSTNAME" \
"aw-watcher-window|Window watcher|Запустите через ansible: ansible aw_windows -i $INVENTORY -m win_shell -a 'Start-Process -FilePath \"C:\\Program Files\\AWatch-rus\\bin\\aw-watcher-window\\aw-watcher-window.exe\" -ArgumentList @(\"--host\", \"10.10.10.13\", \"--port\", \"5600\") -WindowStyle Hidden'" \ "aw-watcher-window|Window watcher|Запустите через ansible: ansible aw_windows -i $INVENTORY -m win_shell -a 'Start-Process -FilePath \"C:\\Program Files\\AWatch-rus\\bin\\aw-watcher-window\\aw-watcher-window.exe\" -ArgumentList @(\"--host\", \"10.10.10.13\", \"--port\", \"5600\") -WindowStyle Hidden'" \
"aw-worktime-sessions|Worktime sessions|Проверьте работу worktime-api: systemctl status aw-worktime-api на AW сервере" \ "aw-worktime-sessions|Worktime sessions|Проверьте работу worktime-api: systemctl status aw-worktime-api на AW сервере" \
"aw-session-events|Session events|Проверьте collector-guard и aw-session-events-collector на RDP" \ "aw-session-events|Session events|Проверьте collector-guard и aw-session-events-collector на RDP" \
"aw-dlp-endpoint-signals|DLP signals|Запустите: ansible aw_windows -i $INVENTORY -m win_shell -a 'schtasks /Run /TN \"ActivityWatch Launch [SHARKON2025_Администратор]\"; Start-Sleep 30'" \ "aw-dlp-endpoint-signals|DLP signals|Запустите: ansible aw_windows -i $INVENTORY -m win_shell -a 'schtasks /Run /TN \"ActivityWatch Recovery\"; Start-Sleep 30'" \
"aw-dlp-incidents|DLP incidents|Проверьте aw-detmir-dlp-collector.ps1 на RDP (логи: C:\\ProgramData\\AWatch-rus\\logs\\)" \ "aw-dlp-incidents|DLP incidents|Проверьте aw-detmir-dlp-collector.ps1 на RDP (логи: C:\\ProgramData\\AWatch-rus\\logs\\)" \
; do ; do
bucket="${entry%%|*}"; rest="${entry#*|}" bucket="${entry%%|*}"; rest="${entry#*|}"
@@ -302,10 +313,10 @@ section "4. Buckets (свежесть данных)"
# ============================================================ # ============================================================
for entry in \ for entry in \
"aw-watcher-afk|AFK watcher|Запустите на RDP: ansible aw_windows -i $INVENTORY -m win_shell -a 'schtasks /Run /TN \"ActivityWatch Recovery\"'" \ "aw-watcher-afk|AFK watcher|Запустите на RDP: ansible aw_windows -i $INVENTORY -m win_shell -a 'schtasks /Run /TN \"ActivityWatch Recovery\"'" \
"aw-watcher-window|Window watcher|Запустите: ansible aw_windows -i $INVENTORY -m win_shell -a 'schtasks /Run /TN \"ActivityWatch Launch [SHARKON2025_Администратор]\"; Start-Process -FilePath \"C:\\Program Files\\AWatch-rus\\bin\\aw-watcher-window\\aw-watcher-window.exe\" -ArgumentList @(\"--host\", \"10.10.10.13\", \"--port\", \"5600\") -WindowStyle Hidden'" \ "aw-watcher-window|Window watcher|Запустите: ansible aw_windows -i $INVENTORY -m win_shell -a 'schtasks /Run /TN \"ActivityWatch Recovery\"; Start-Sleep 60'" \
"aw-worktime-sessions|Worktime sessions|Проверьте: ssh igor@$AW_HOST 'systemctl status aw-worktime-api && journalctl -u aw-worktime-api -n 20'" \ "aw-worktime-sessions|Worktime sessions|Проверьте: ssh igor@$AW_HOST 'systemctl status aw-worktime-api && journalctl -u aw-worktime-api -n 20'" \
"aw-session-events|Session events|Проверьте collector-guard на RDP: ansible aw_windows -i $INVENTORY -m win_shell -a 'Get-Process -Name aw-session-events-* -ErrorAction SilentlyContinue'" \ "aw-session-events|Session events|Проверьте collector-guard на RDP: ansible aw_windows -i $INVENTORY -m win_shell -a 'Get-Process -Name aw-session-events-* -ErrorAction SilentlyContinue'" \
"aw-dlp-endpoint-signals|DLP endpoint signals|Запустите: ansible aw_windows -i $INVENTORY -m win_shell -a 'schtasks /Run /TN \"ActivityWatch Launch [SHARKON2025_Администратор]\"; Start-Sleep 60'" \ "aw-dlp-endpoint-signals|DLP endpoint signals|Запустите: ansible aw_windows -i $INVENTORY -m win_shell -a 'schtasks /Run /TN \"ActivityWatch Recovery\"; Start-Sleep 60'" \
"aw-dlp-incidents|DLP incidents|Проверьте: ansible aw_windows -i $INVENTORY -m win_shell -a \"Get-Content 'C:\\ProgramData\\AWatch-rus\\logs\\dlp-*.log' -Tail 20\"" \ "aw-dlp-incidents|DLP incidents|Проверьте: ansible aw_windows -i $INVENTORY -m win_shell -a \"Get-Content 'C:\\ProgramData\\AWatch-rus\\logs\\dlp-*.log' -Tail 20\"" \
"aw-dlp-review|DLP review|Проверьте: ssh igor@$AW_HOST 'journalctl -u aw-dlp-policy-engine.service -n 20 --no-pager'" \ "aw-dlp-review|DLP review|Проверьте: ssh igor@$AW_HOST 'journalctl -u aw-dlp-policy-engine.service -n 20 --no-pager'" \
"aw-dlp-rules|DLP rules|Проверьте: ssh igor@$AW_HOST 'journalctl -u aw-dlp-ioc-refresh.service -n 20 --no-pager'" \ "aw-dlp-rules|DLP rules|Проверьте: ssh igor@$AW_HOST 'journalctl -u aw-dlp-ioc-refresh.service -n 20 --no-pager'" \
@@ -389,6 +400,9 @@ check_service_remote "Nginx service" "$PROXMOX_HOST" "nginx.service" \
# ============================================================ # ============================================================
section "10. DLP Pipeline (10.10.10.13)" section "10. DLP Pipeline (10.10.10.13)"
# ============================================================ # ============================================================
if [ "$DLP_ENABLED" = "false" ]; then
skip "DLP Pipeline disabled by AW_DLP_ENABLED/DETMIR_DLP_ENABLED=false"
else
# DLP Policy Engine — должен быть active (running) # DLP Policy Engine — должен быть active (running)
check_service_remote "DLP Policy Engine" "$AW_HOST" "aw-dlp-policy-engine.service" \ check_service_remote "DLP Policy Engine" "$AW_HOST" "aw-dlp-policy-engine.service" \
"Проверьте: ssh igor@$AW_HOST 'sudo journalctl -u aw-dlp-policy-engine.service -n 30 --no-pager'" "Проверьте: ssh igor@$AW_HOST 'sudo journalctl -u aw-dlp-policy-engine.service -n 30 --no-pager'"
@@ -434,6 +448,7 @@ check_service_remote "DLP Webhook Sender timer" "$AW_HOST" "aw-dlp-webhook-sende
# DLP Report Scheduler # DLP Report Scheduler
check_service_remote "DLP Report Scheduler timer" "$AW_HOST" "aw-dlp-report-scheduler.timer" \ check_service_remote "DLP Report Scheduler timer" "$AW_HOST" "aw-dlp-report-scheduler.timer" \
"ssh igor@$AW_HOST 'sudo systemctl enable --now aw-dlp-report-scheduler.timer'" "ssh igor@$AW_HOST 'sudo systemctl enable --now aw-dlp-report-scheduler.timer'"
fi
# Worktime Influx Exporter # Worktime Influx Exporter
check_service_remote "Worktime Influx Exporter timer" "$AW_HOST" "aw-worktime-influx-exporter.timer" \ check_service_remote "Worktime Influx Exporter timer" "$AW_HOST" "aw-worktime-influx-exporter.timer" \
@@ -443,7 +458,7 @@ check_service_remote "Worktime Influx Exporter timer" "$AW_HOST" "aw-worktime-in
if [ "$SKIP_WINDOWS" = "1" ]; then if [ "$SKIP_WINDOWS" = "1" ]; then
skip "Проверки RDP хоста пропущены (--skip-windows)" skip "Проверки RDP хоста пропущены (--skip-windows)"
else else
section "11. RDP хост (192.168.100.18)" section "11. RDP хост ($WINDOWS_HOST)"
if have ansible && [ -f "$INVENTORY" ]; then if have ansible && [ -f "$INVENTORY" ]; then
check_ansible_module "WinRM ping" aw_windows win_ping check_ansible_module "WinRM ping" aw_windows win_ping
@@ -459,7 +474,7 @@ else
'schtasks /Query /TN "ActivityWatch Recovery" /FO LIST /V | Select-String "Status|Run|Next"' 'schtasks /Query /TN "ActivityWatch Recovery" /FO LIST /V | Select-String "Status|Run|Next"'
check_ansible_win_shell "Scheduled tasks (Launch Admin)" \ check_ansible_win_shell "Scheduled tasks (Launch Admin)" \
'schtasks /Query /TN "ActivityWatch Launch [SHARKON2025_Администратор]" /FO LIST /V | Select-String "Status|Run|Next"' 'Get-Content -Raw "C:\ProgramData\AWatch-rus\deployment-config.json" | ConvertFrom-Json | Select-Object -ExpandProperty userTasks | Select-Object LaunchTaskName,UserId | Format-Table -AutoSize'
else else
skip "Ansible или inventory не найдены" skip "Ansible или inventory не найдены"
fi fi
@@ -469,7 +484,7 @@ fi
section "12. Systemd health (AW server)" section "12. Systemd health (AW server)"
# ============================================================ # ============================================================
check_ansible_shell "AW server core units" aw_server \ check_ansible_shell "AW server core units" aw_server \
'systemctl is-active activitywatch-server aw-worktime-api aw-dlp-policy-engine aw-dlp-case-management aw-worktime-influx-exporter.timer aw-dlp-influx-exporter.timer activitywatch-dlp-aggregator.timer aw-clickhouse-network-health.timer | paste -sd,' 'if [ "${AW_DLP_ENABLED:-true}" = "false" ] || [ "${DETMIR_DLP_ENABLED:-true}" = "false" ]; then systemctl is-active activitywatch-server aw-worktime-api aw-worktime-influx-exporter.timer aw-clickhouse-network-health.timer | paste -sd,; else systemctl is-active activitywatch-server aw-worktime-api aw-dlp-policy-engine aw-dlp-case-management aw-worktime-influx-exporter.timer aw-dlp-influx-exporter.timer activitywatch-dlp-aggregator.timer aw-clickhouse-network-health.timer | paste -sd,; fi'
check_ansible_shell "AW server — нет failed units" aw_server \ check_ansible_shell "AW server — нет failed units" aw_server \
'failed=$(systemctl --failed --no-legend | awk "{print \$1}" | grep -E "activitywatch|aw-|dlp" || true); test -z "$failed" && echo "no AW-related failed units" || { echo "$failed"; exit 1; }' 'failed=$(systemctl --failed --no-legend | awk "{print \$1}" | grep -E "activitywatch|aw-|dlp" || true); test -z "$failed" && echo "no AW-related failed units" || { echo "$failed"; exit 1; }'
+17 -5
View File
@@ -40,12 +40,13 @@ GRAFANA_PASSWORD="${GRAFANA_PASSWORD:-}"
PROXMOX_HOST="${AW_SMOKE_PROXMOX_HOST:-10.10.10.2}" PROXMOX_HOST="${AW_SMOKE_PROXMOX_HOST:-10.10.10.2}"
AW_HOST="${AW_SMOKE_AW_HOST:-10.10.10.13}" AW_HOST="${AW_SMOKE_AW_HOST:-10.10.10.13}"
GRAFANA_HOST="${AW_SMOKE_GRAFANA_HOST:-10.10.10.11}" GRAFANA_HOST="${AW_SMOKE_GRAFANA_HOST:-10.10.10.11}"
WINDOWS_HOST="${AW_SMOKE_WINDOWS_HOST:-192.168.100.18}" WINDOWS_HOST="${AW_SMOKE_WINDOWS_HOST:-${AW_WINDOWS_HOST:-192.168.100.19}}"
AW_SOURCE_HOSTNAME="${AW_SMOKE_SOURCE_HOSTNAME:-SHARKON2025}" AW_SOURCE_HOSTNAME="${AW_SMOKE_SOURCE_HOSTNAME:-${AW_LOGICAL_HOST_ID:-${AW_MONITORED_WINDOWS_HOSTNAME:-SHARKON2025}}}"
LOG_DIR="${AW_SMOKE_LOG_DIR:-$REPO_ROOT/output/smoke}" LOG_DIR="${AW_SMOKE_LOG_DIR:-$REPO_ROOT/output/smoke}"
RUN_REMOTE="${AW_SMOKE_RUN_REMOTE:-1}" RUN_REMOTE="${AW_SMOKE_RUN_REMOTE:-1}"
RUN_WINRM="${AW_SMOKE_RUN_WINRM:-1}" RUN_WINRM="${AW_SMOKE_RUN_WINRM:-1}"
RUN_SERVER_SYSTEMD="${AW_SMOKE_RUN_SERVER_SYSTEMD:-1}" RUN_SERVER_SYSTEMD="${AW_SMOKE_RUN_SERVER_SYSTEMD:-1}"
LOKI_ENABLED="${AW_SMOKE_LOKI_ENABLED:-0}"
NO_PROXY_REQUIRED="localhost,127.0.0.1,$PROXMOX_HOST,$AW_HOST,$GRAFANA_HOST,$WINDOWS_HOST,10.10.10.0/24,192.168.100.0/24" NO_PROXY_REQUIRED="localhost,127.0.0.1,$PROXMOX_HOST,$AW_HOST,$GRAFANA_HOST,$WINDOWS_HOST,10.10.10.0/24,192.168.100.0/24"
if [ -n "${no_proxy:-}" ]; then if [ -n "${no_proxy:-}" ]; then
@@ -87,6 +88,7 @@ Environment overrides:
AW_SMOKE_WORKTIME_API=http://10.10.10.13:5610 AW_SMOKE_WORKTIME_API=http://10.10.10.13:5610
AW_SMOKE_GRAFANA_URL=http://10.10.10.11:3000 AW_SMOKE_GRAFANA_URL=http://10.10.10.11:3000
AW_SMOKE_SOURCE_HOSTNAME=SHARKON2025 AW_SMOKE_SOURCE_HOSTNAME=SHARKON2025
AW_SMOKE_LOKI_ENABLED=0|1
AW_SMOKE_ENV_FILE=$HOME/.config/aw-contour-smoke.env AW_SMOKE_ENV_FILE=$HOME/.config/aw-contour-smoke.env
AW_SMOKE_LOG_DIR=$REPO_ROOT/output/smoke AW_SMOKE_LOG_DIR=$REPO_ROOT/output/smoke
GRAFANA_USER/GRAFANA_PASSWORD via env or a local env file GRAFANA_USER/GRAFANA_PASSWORD via env or a local env file
@@ -219,6 +221,10 @@ check_http_code_basic_auth() {
check_grafana_loki_proxy() { check_grafana_loki_proxy() {
local name="Grafana Loki datasource proxy" local name="Grafana Loki datasource proxy"
local tmp uid code body local tmp uid code body
if [ "$LOKI_ENABLED" != "1" ]; then
skip "$name skipped: Loki is intentionally disabled for the current DetMir resource profile"
return
fi
tmp="$(mktemp)" tmp="$(mktemp)"
if ! curl -k -fsS -u "$GRAFANA_USER:$GRAFANA_PASSWORD" --connect-timeout 5 --max-time 20 "$GRAFANA_URL/api/datasources" -o "$tmp" 2>"$tmp.err"; then if ! curl -k -fsS -u "$GRAFANA_USER:$GRAFANA_PASSWORD" --connect-timeout 5 --max-time 20 "$GRAFANA_URL/api/datasources" -o "$tmp" 2>"$tmp.err"; then
fail "$name cannot list datasources" fail "$name cannot list datasources"
@@ -416,7 +422,7 @@ classify_bucket_age() {
check_bucket_freshness() { check_bucket_freshness() {
local bucket="$1" local bucket="$1"
local bucket_id="${bucket}_${AW_SOURCE_HOSTNAME}" local bucket_id="${bucket}_${AW_SOURCE_HOSTNAME}"
local tmp last_ts last_id event_epoch now age_sec status local tmp last_ts last_id event_epoch now age_sec status meta_ts
tmp="$(mktemp)" tmp="$(mktemp)"
if ! curl -fsS --connect-timeout 5 --max-time 20 "$AW_SERVER/api/0/buckets/$bucket_id/events?limit=1" -o "$tmp" 2>"$tmp.err"; then if ! curl -fsS --connect-timeout 5 --max-time 20 "$AW_SERVER/api/0/buckets/$bucket_id/events?limit=1" -o "$tmp" 2>"$tmp.err"; then
fail "bucket $bucket_id query failed" fail "bucket $bucket_id query failed"
@@ -428,6 +434,12 @@ check_bucket_freshness() {
last_ts="$(jq -r '.[0].timestamp // empty' "$tmp" 2>/dev/null)" last_ts="$(jq -r '.[0].timestamp // empty' "$tmp" 2>/dev/null)"
last_id="$(jq -r '.[0].id // 0' "$tmp" 2>/dev/null)" last_id="$(jq -r '.[0].id // 0' "$tmp" 2>/dev/null)"
rm -f "$tmp" "$tmp.err" rm -f "$tmp" "$tmp.err"
if [ "$bucket" = "aw-watcher-afk" ]; then
meta_ts="$(curl -fsS --connect-timeout 5 --max-time 20 "$AW_SERVER/api/0/buckets/$bucket_id" 2>/dev/null | jq -r '.metadata.end // empty' 2>/dev/null || true)"
if [ -n "$meta_ts" ]; then
last_ts="$meta_ts"
fi
fi
if [ -z "$last_ts" ]; then if [ -z "$last_ts" ]; then
case "$bucket" in case "$bucket" in
@@ -611,7 +623,7 @@ if [ "$RUN_WINRM" = "1" ] && have ansible; then
check_ansible_module "Windows win_ping" aw_windows win_ping check_ansible_module "Windows win_ping" aw_windows win_ping
check_ansible_win_shell "Windows sessions" aw_windows '$psi = [System.Diagnostics.ProcessStartInfo]::new(); $psi.FileName = "$env:SystemRoot\System32\query.exe"; $psi.Arguments = "user"; $psi.UseShellExecute = $false; $psi.RedirectStandardOutput = $true; $psi.RedirectStandardError = $true; $psi.StandardOutputEncoding = [System.Text.Encoding]::GetEncoding(866); $psi.StandardErrorEncoding = [System.Text.Encoding]::GetEncoding(866); $p = [System.Diagnostics.Process]::Start($psi); $out = $p.StandardOutput.ReadToEnd(); $err = $p.StandardError.ReadToEnd(); $p.WaitForExit(); [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false); $out; if ($err) { $err }; if ($out -match "USERNAME|ПОЛЬЗОВАТЕЛЬ|администратор|Администратор") { exit 0 } else { exit $p.ExitCode }' check_ansible_win_shell "Windows sessions" aw_windows '$psi = [System.Diagnostics.ProcessStartInfo]::new(); $psi.FileName = "$env:SystemRoot\System32\query.exe"; $psi.Arguments = "user"; $psi.UseShellExecute = $false; $psi.RedirectStandardOutput = $true; $psi.RedirectStandardError = $true; $psi.StandardOutputEncoding = [System.Text.Encoding]::GetEncoding(866); $psi.StandardErrorEncoding = [System.Text.Encoding]::GetEncoding(866); $p = [System.Diagnostics.Process]::Start($psi); $out = $p.StandardOutput.ReadToEnd(); $err = $p.StandardError.ReadToEnd(); $p.WaitForExit(); [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false); $out; if ($err) { $err }; if ($out -match "USERNAME|ПОЛЬЗОВАТЕЛЬ|администратор|Администратор") { exit 0 } else { exit $p.ExitCode }'
check_ansible_win_shell_warn "Windows collector processes" aw_windows '$p = Get-Process aw-watcher-afk,aw-watcher-window -ErrorAction SilentlyContinue; if ($p) { $p | Select-Object Name,Id,SessionId,StartTime | Format-Table -AutoSize } else { "no aw-watcher-afk/window process visible to this WinRM session" }' check_ansible_win_shell_warn "Windows collector processes" aw_windows '$p = Get-Process aw-watcher-afk,aw-watcher-window -ErrorAction SilentlyContinue; if ($p) { $p | Select-Object Name,Id,SessionId,StartTime | Format-Table -AutoSize } else { "no aw-watcher-afk/window process visible to this WinRM session" }'
check_ansible_win_shell "Windows ActivityWatch tasks" aw_windows 'schtasks /Query /TN "ActivityWatch Recovery" /FO LIST /V; schtasks /Query /TN "ActivityWatch Launch [SHARKON2025_Администратор]" /FO LIST /V' check_ansible_win_shell "Windows ActivityWatch tasks" aw_windows 'schtasks /Query /TN "ActivityWatch Recovery" /FO LIST /V; Get-Content -Raw "C:\ProgramData\AWatch-rus\deployment-config.json" | ConvertFrom-Json | Select-Object -ExpandProperty userTasks | Select-Object LaunchTaskName,UserId | Format-Table -AutoSize'
else else
skip "Windows WinRM checks skipped" skip "Windows WinRM checks skipped"
fi fi
@@ -625,7 +637,7 @@ else
skip "check-aw-data.sh missing" skip "check-aw-data.sh missing"
fi fi
if [ -x "$REPO_ROOT/check-aw-full.sh" ]; then if [ -x "$REPO_ROOT/check-aw-full.sh" ]; then
if "$REPO_ROOT/check-aw-full.sh"; then pass "check-aw-full.sh completed"; else fail "check-aw-full.sh failed"; fi if AW_SMOKE_AW_SERVER="$AW_SERVER" AW_SMOKE_SOURCE_HOSTNAME="$AW_SOURCE_HOSTNAME" AW_SMOKE_WINDOWS_HOST="$WINDOWS_HOST" "$REPO_ROOT/check-aw-full.sh"; then pass "check-aw-full.sh completed"; else fail "check-aw-full.sh failed"; fi
else else
skip "check-aw-full.sh missing" skip "check-aw-full.sh missing"
fi fi
@@ -23,9 +23,14 @@ PROXMOX_HOST="10.10.10.2"
AW_HOST="10.10.10.13" AW_HOST="10.10.10.13"
GRAFANA_HOST="10.10.10.11" GRAFANA_HOST="10.10.10.11"
INFLUXDB_HOST="10.10.10.10" INFLUXDB_HOST="10.10.10.10"
WINDOWS_HOST="192.168.100.18" WINDOWS_HOST="${AW_WINDOWS_HOST:-${AW_SMOKE_WINDOWS_HOST:-192.168.100.19}}"
CLICKHOUSE_HOST="10.10.10.2" CLICKHOUSE_HOST="10.10.10.2"
SOURCE_HOSTNAME="SHARKON2025" SOURCE_HOSTNAME="${AW_LOGICAL_HOST_ID:-${AW_MONITORED_WINDOWS_HOSTNAME:-${AW_SMOKE_SOURCE_HOSTNAME:-SHARKON2025}}}"
DLP_ENABLED="${AW_DLP_ENABLED:-${DETMIR_DLP_ENABLED:-true}}"
case "${DLP_ENABLED,,}" in
0|false|no|off) DLP_ENABLED=false ;;
*) DLP_ENABLED=true ;;
esac
QUICK_MODE=0 QUICK_MODE=0
SKIP_WINDOWS=0 SKIP_WINDOWS=0
@@ -102,7 +107,7 @@ check_http_json_key() {
check_bucket_freshness() { check_bucket_freshness() {
local bucket="$1" label="$2" remediation="$3" local bucket="$1" label="$2" remediation="$3"
local bucket_id="${bucket}_${SOURCE_HOSTNAME}" local bucket_id="${bucket}_${SOURCE_HOSTNAME}"
local tmp last_ts event_epoch now age_sec local tmp last_ts event_epoch now age_sec meta_ts
tmp="$(mktemp)" tmp="$(mktemp)"
if ! curl -fsS --connect-timeout 5 --max-time 15 "$AW_SERVER/api/0/buckets/$bucket_id/events?limit=1" -o "$tmp" 2>"$tmp.err"; then if ! curl -fsS --connect-timeout 5 --max-time 15 "$AW_SERVER/api/0/buckets/$bucket_id/events?limit=1" -o "$tmp" 2>"$tmp.err"; then
fail "bucket $label ($bucket_id) — запрос не удался" fail "bucket $label ($bucket_id) — запрос не удался"
@@ -113,6 +118,12 @@ check_bucket_freshness() {
fi fi
last_ts="$(jq -r '.[0].timestamp // empty' "$tmp" 2>/dev/null)" last_ts="$(jq -r '.[0].timestamp // empty' "$tmp" 2>/dev/null)"
rm -f "$tmp" rm -f "$tmp"
if [ "$bucket" = "aw-watcher-afk" ]; then
meta_ts="$(curl -fsS --connect-timeout 5 --max-time 15 "$AW_SERVER/api/0/buckets/$bucket_id" 2>/dev/null | jq -r '.metadata.end // empty' 2>/dev/null || true)"
if [ -n "$meta_ts" ]; then
last_ts="$meta_ts"
fi
fi
if [ -z "$last_ts" ]; then if [ -z "$last_ts" ]; then
warn "bucket $label ($bucket_id) — нет событий" warn "bucket $label ($bucket_id) — нет событий"
echo " REMEDIATION: $remediation" echo " REMEDIATION: $remediation"
@@ -268,11 +279,11 @@ if [ "$QUICK_MODE" = "1" ]; then
section "4. Buckets (быстрый режим)" section "4. Buckets (быстрый режим)"
for entry in \ for entry in \
"aw-watcher-afk|AFK watcher|Запустите на RDP: schtasks /Run /TN \"ActivityWatch Recovery\" или schtasks /Run /TN \"ActivityWatch Launch [SHARKON2025_Администратор]\"" \ "aw-watcher-afk|AFK watcher|Запустите на RDP: schtasks /Run /TN \"ActivityWatch Recovery\" или launch task для logical host $SOURCE_HOSTNAME" \
"aw-watcher-window|Window watcher|Запустите через ansible: ansible aw_windows -i $INVENTORY -m win_shell -a 'Start-Process -FilePath \"C:\\Program Files\\AWatch-rus\\bin\\aw-watcher-window\\aw-watcher-window.exe\" -ArgumentList @(\"--host\", \"10.10.10.13\", \"--port\", \"5600\") -WindowStyle Hidden'" \ "aw-watcher-window|Window watcher|Запустите через ansible: ansible aw_windows -i $INVENTORY -m win_shell -a 'Start-Process -FilePath \"C:\\Program Files\\AWatch-rus\\bin\\aw-watcher-window\\aw-watcher-window.exe\" -ArgumentList @(\"--host\", \"10.10.10.13\", \"--port\", \"5600\") -WindowStyle Hidden'" \
"aw-worktime-sessions|Worktime sessions|Проверьте работу worktime-api: systemctl status aw-worktime-api на AW сервере" \ "aw-worktime-sessions|Worktime sessions|Проверьте работу worktime-api: systemctl status aw-worktime-api на AW сервере" \
"aw-session-events|Session events|Проверьте collector-guard и aw-session-events-collector на RDP" \ "aw-session-events|Session events|Проверьте collector-guard и aw-session-events-collector на RDP" \
"aw-dlp-endpoint-signals|DLP signals|Запустите: ansible aw_windows -i $INVENTORY -m win_shell -a 'schtasks /Run /TN \"ActivityWatch Launch [SHARKON2025_Администратор]\"; Start-Sleep 30'" \ "aw-dlp-endpoint-signals|DLP signals|Запустите: ansible aw_windows -i $INVENTORY -m win_shell -a 'schtasks /Run /TN \"ActivityWatch Recovery\"; Start-Sleep 30'" \
"aw-dlp-incidents|DLP incidents|Проверьте aw-detmir-dlp-collector.ps1 на RDP (логи: C:\\ProgramData\\AWatch-rus\\logs\\)" \ "aw-dlp-incidents|DLP incidents|Проверьте aw-detmir-dlp-collector.ps1 на RDP (логи: C:\\ProgramData\\AWatch-rus\\logs\\)" \
; do ; do
bucket="${entry%%|*}"; rest="${entry#*|}" bucket="${entry%%|*}"; rest="${entry#*|}"
@@ -302,10 +313,10 @@ section "4. Buckets (свежесть данных)"
# ============================================================ # ============================================================
for entry in \ for entry in \
"aw-watcher-afk|AFK watcher|Запустите на RDP: ansible aw_windows -i $INVENTORY -m win_shell -a 'schtasks /Run /TN \"ActivityWatch Recovery\"'" \ "aw-watcher-afk|AFK watcher|Запустите на RDP: ansible aw_windows -i $INVENTORY -m win_shell -a 'schtasks /Run /TN \"ActivityWatch Recovery\"'" \
"aw-watcher-window|Window watcher|Запустите: ansible aw_windows -i $INVENTORY -m win_shell -a 'schtasks /Run /TN \"ActivityWatch Launch [SHARKON2025_Администратор]\"; Start-Process -FilePath \"C:\\Program Files\\AWatch-rus\\bin\\aw-watcher-window\\aw-watcher-window.exe\" -ArgumentList @(\"--host\", \"10.10.10.13\", \"--port\", \"5600\") -WindowStyle Hidden'" \ "aw-watcher-window|Window watcher|Запустите: ansible aw_windows -i $INVENTORY -m win_shell -a 'schtasks /Run /TN \"ActivityWatch Recovery\"; Start-Sleep 60'" \
"aw-worktime-sessions|Worktime sessions|Проверьте: ssh igor@$AW_HOST 'systemctl status aw-worktime-api && journalctl -u aw-worktime-api -n 20'" \ "aw-worktime-sessions|Worktime sessions|Проверьте: ssh igor@$AW_HOST 'systemctl status aw-worktime-api && journalctl -u aw-worktime-api -n 20'" \
"aw-session-events|Session events|Проверьте collector-guard на RDP: ansible aw_windows -i $INVENTORY -m win_shell -a 'Get-Process -Name aw-session-events-* -ErrorAction SilentlyContinue'" \ "aw-session-events|Session events|Проверьте collector-guard на RDP: ansible aw_windows -i $INVENTORY -m win_shell -a 'Get-Process -Name aw-session-events-* -ErrorAction SilentlyContinue'" \
"aw-dlp-endpoint-signals|DLP endpoint signals|Запустите: ansible aw_windows -i $INVENTORY -m win_shell -a 'schtasks /Run /TN \"ActivityWatch Launch [SHARKON2025_Администратор]\"; Start-Sleep 60'" \ "aw-dlp-endpoint-signals|DLP endpoint signals|Запустите: ansible aw_windows -i $INVENTORY -m win_shell -a 'schtasks /Run /TN \"ActivityWatch Recovery\"; Start-Sleep 60'" \
"aw-dlp-incidents|DLP incidents|Проверьте: ansible aw_windows -i $INVENTORY -m win_shell -a \"Get-Content 'C:\\ProgramData\\AWatch-rus\\logs\\dlp-*.log' -Tail 20\"" \ "aw-dlp-incidents|DLP incidents|Проверьте: ansible aw_windows -i $INVENTORY -m win_shell -a \"Get-Content 'C:\\ProgramData\\AWatch-rus\\logs\\dlp-*.log' -Tail 20\"" \
"aw-dlp-review|DLP review|Проверьте: ssh igor@$AW_HOST 'journalctl -u aw-dlp-policy-engine.service -n 20 --no-pager'" \ "aw-dlp-review|DLP review|Проверьте: ssh igor@$AW_HOST 'journalctl -u aw-dlp-policy-engine.service -n 20 --no-pager'" \
"aw-dlp-rules|DLP rules|Проверьте: ssh igor@$AW_HOST 'journalctl -u aw-dlp-ioc-refresh.service -n 20 --no-pager'" \ "aw-dlp-rules|DLP rules|Проверьте: ssh igor@$AW_HOST 'journalctl -u aw-dlp-ioc-refresh.service -n 20 --no-pager'" \
@@ -389,6 +400,9 @@ check_service_remote "Nginx service" "$PROXMOX_HOST" "nginx.service" \
# ============================================================ # ============================================================
section "10. DLP Pipeline (10.10.10.13)" section "10. DLP Pipeline (10.10.10.13)"
# ============================================================ # ============================================================
if [ "$DLP_ENABLED" = "false" ]; then
skip "DLP Pipeline disabled by AW_DLP_ENABLED/DETMIR_DLP_ENABLED=false"
else
# DLP Policy Engine — должен быть active (running) # DLP Policy Engine — должен быть active (running)
check_service_remote "DLP Policy Engine" "$AW_HOST" "aw-dlp-policy-engine.service" \ check_service_remote "DLP Policy Engine" "$AW_HOST" "aw-dlp-policy-engine.service" \
"Проверьте: ssh igor@$AW_HOST 'sudo journalctl -u aw-dlp-policy-engine.service -n 30 --no-pager'" "Проверьте: ssh igor@$AW_HOST 'sudo journalctl -u aw-dlp-policy-engine.service -n 30 --no-pager'"
@@ -434,6 +448,7 @@ check_service_remote "DLP Webhook Sender timer" "$AW_HOST" "aw-dlp-webhook-sende
# DLP Report Scheduler # DLP Report Scheduler
check_service_remote "DLP Report Scheduler timer" "$AW_HOST" "aw-dlp-report-scheduler.timer" \ check_service_remote "DLP Report Scheduler timer" "$AW_HOST" "aw-dlp-report-scheduler.timer" \
"ssh igor@$AW_HOST 'sudo systemctl enable --now aw-dlp-report-scheduler.timer'" "ssh igor@$AW_HOST 'sudo systemctl enable --now aw-dlp-report-scheduler.timer'"
fi
# Worktime Influx Exporter # Worktime Influx Exporter
check_service_remote "Worktime Influx Exporter timer" "$AW_HOST" "aw-worktime-influx-exporter.timer" \ check_service_remote "Worktime Influx Exporter timer" "$AW_HOST" "aw-worktime-influx-exporter.timer" \
@@ -443,7 +458,7 @@ check_service_remote "Worktime Influx Exporter timer" "$AW_HOST" "aw-worktime-in
if [ "$SKIP_WINDOWS" = "1" ]; then if [ "$SKIP_WINDOWS" = "1" ]; then
skip "Проверки RDP хоста пропущены (--skip-windows)" skip "Проверки RDP хоста пропущены (--skip-windows)"
else else
section "11. RDP хост (192.168.100.18)" section "11. RDP хост ($WINDOWS_HOST)"
if have ansible && [ -f "$INVENTORY" ]; then if have ansible && [ -f "$INVENTORY" ]; then
check_ansible_module "WinRM ping" aw_windows win_ping check_ansible_module "WinRM ping" aw_windows win_ping
@@ -459,7 +474,7 @@ else
'schtasks /Query /TN "ActivityWatch Recovery" /FO LIST /V | Select-String "Status|Run|Next"' 'schtasks /Query /TN "ActivityWatch Recovery" /FO LIST /V | Select-String "Status|Run|Next"'
check_ansible_win_shell "Scheduled tasks (Launch Admin)" \ check_ansible_win_shell "Scheduled tasks (Launch Admin)" \
'schtasks /Query /TN "ActivityWatch Launch [SHARKON2025_Администратор]" /FO LIST /V | Select-String "Status|Run|Next"' 'Get-Content -Raw "C:\ProgramData\AWatch-rus\deployment-config.json" | ConvertFrom-Json | Select-Object -ExpandProperty userTasks | Select-Object LaunchTaskName,UserId | Format-Table -AutoSize'
else else
skip "Ansible или inventory не найдены" skip "Ansible или inventory не найдены"
fi fi
@@ -469,7 +484,7 @@ fi
section "12. Systemd health (AW server)" section "12. Systemd health (AW server)"
# ============================================================ # ============================================================
check_ansible_shell "AW server core units" aw_server \ check_ansible_shell "AW server core units" aw_server \
'systemctl is-active activitywatch-server aw-worktime-api aw-dlp-policy-engine aw-dlp-case-management aw-worktime-influx-exporter.timer aw-dlp-influx-exporter.timer activitywatch-dlp-aggregator.timer aw-clickhouse-network-health.timer | paste -sd,' 'if [ "${AW_DLP_ENABLED:-true}" = "false" ] || [ "${DETMIR_DLP_ENABLED:-true}" = "false" ]; then systemctl is-active activitywatch-server aw-worktime-api aw-worktime-influx-exporter.timer aw-clickhouse-network-health.timer | paste -sd,; else systemctl is-active activitywatch-server aw-worktime-api aw-dlp-policy-engine aw-dlp-case-management aw-worktime-influx-exporter.timer aw-dlp-influx-exporter.timer activitywatch-dlp-aggregator.timer aw-clickhouse-network-health.timer | paste -sd,; fi'
check_ansible_shell "AW server — нет failed units" aw_server \ check_ansible_shell "AW server — нет failed units" aw_server \
'failed=$(systemctl --failed --no-legend | awk "{print \$1}" | grep -E "activitywatch|aw-|dlp" || true); test -z "$failed" && echo "no AW-related failed units" || { echo "$failed"; exit 1; }' 'failed=$(systemctl --failed --no-legend | awk "{print \$1}" | grep -E "activitywatch|aw-|dlp" || true); test -z "$failed" && echo "no AW-related failed units" || { echo "$failed"; exit 1; }'
+1 -1
View File
@@ -79,7 +79,7 @@ fi
DETMIR_SUPPORT_PVE_HOST="${DETMIR_SUPPORT_PVE_HOST:-10.10.10.2}" DETMIR_SUPPORT_PVE_HOST="${DETMIR_SUPPORT_PVE_HOST:-10.10.10.2}"
DETMIR_SUPPORT_AW_HOST="${DETMIR_SUPPORT_AW_HOST:-10.10.10.13}" DETMIR_SUPPORT_AW_HOST="${DETMIR_SUPPORT_AW_HOST:-10.10.10.13}"
DETMIR_SUPPORT_WEB_HOST="${DETMIR_SUPPORT_WEB_HOST:-$DETMIR_SUPPORT_PVE_HOST}" DETMIR_SUPPORT_WEB_HOST="${DETMIR_SUPPORT_WEB_HOST:-$DETMIR_SUPPORT_PVE_HOST}"
DETMIR_SUPPORT_WINDOWS_HOST="${DETMIR_SUPPORT_WINDOWS_HOST:-192.168.100.18}" DETMIR_SUPPORT_WINDOWS_HOST="${DETMIR_SUPPORT_WINDOWS_HOST:-192.168.100.19}"
DETMIR_SUPPORT_PFSENSE_HOST="${DETMIR_SUPPORT_PFSENSE_HOST:-}" DETMIR_SUPPORT_PFSENSE_HOST="${DETMIR_SUPPORT_PFSENSE_HOST:-}"
DETMIR_SUPPORT_PFSENSE_URL="${DETMIR_SUPPORT_PFSENSE_URL:-}" DETMIR_SUPPORT_PFSENSE_URL="${DETMIR_SUPPORT_PFSENSE_URL:-}"
DETMIR_SUPPORT_OPENVPN_HOST="${DETMIR_SUPPORT_OPENVPN_HOST:-}" DETMIR_SUPPORT_OPENVPN_HOST="${DETMIR_SUPPORT_OPENVPN_HOST:-}"
+1 -1
View File
@@ -2,7 +2,7 @@
DETMIR_SUPPORT_PVE_HOST=10.10.10.2 DETMIR_SUPPORT_PVE_HOST=10.10.10.2
DETMIR_SUPPORT_AW_HOST=10.10.10.13 DETMIR_SUPPORT_AW_HOST=10.10.10.13
DETMIR_SUPPORT_WEB_HOST=10.10.10.2 DETMIR_SUPPORT_WEB_HOST=10.10.10.2
DETMIR_SUPPORT_WINDOWS_HOST=192.168.100.18 DETMIR_SUPPORT_WINDOWS_HOST=192.168.100.19
## Сетевые компоненты ## Сетевые компоненты
DETMIR_SUPPORT_PFSENSE_HOST=10.0.0.1 DETMIR_SUPPORT_PFSENSE_HOST=10.0.0.1
+97 -16
View File
@@ -239,8 +239,22 @@ if [[ -s "$PUBLIC_ISSUES_MANIFEST" ]]; then
) )
) )
) )
and ([.issues[] | select(.id == "011" and .github_issue_url == "https://github.com/igor04091968/AWatch-rus/issues/48" and .status == "created" and .next_evidence_doc == "docs/PR_REVIEW_EVIDENCE_RU.md")] | length == 1) and ([.issues[] | select(
and ([.issues[] | select(.id == "012" and .github_issue_url == "https://github.com/igor04091968/AWatch-rus/issues/49" and .status == "created" and .next_evidence_doc == "docs/BRANCH_PROTECTION_EVIDENCE_RU.md")] | length == 1) .id == "011"
and .github_issue_url == "https://github.com/igor04091968/AWatch-rus/issues/48"
and .status == "created"
and .next_evidence_doc == "docs/PR_REVIEW_EVIDENCE_RU.md"
and .evidence_status == "pending_review_required"
and .evidence_doc == "docs/PR_REVIEW_EVIDENCE_RU.md"
)] | length == 1)
and ([.issues[] | select(
.id == "012"
and .github_issue_url == "https://github.com/igor04091968/AWatch-rus/issues/49"
and .status == "created"
and .next_evidence_doc == "docs/BRANCH_PROTECTION_EVIDENCE_RU.md"
and .evidence_status == "verified_active_ruleset"
and .evidence_doc == "docs/BRANCH_PROTECTION_EVIDENCE_RU.md"
)] | length == 1)
' "$PUBLIC_ISSUES_MANIFEST" >/dev/null || fail "public_issues_manifest_required_fields" ' "$PUBLIC_ISSUES_MANIFEST" >/dev/null || fail "public_issues_manifest_required_fields"
elif command -v python3 >/dev/null 2>&1; then elif command -v python3 >/dev/null 2>&1; then
python3 - "$PUBLIC_ISSUES_MANIFEST" <<'PY' || fail "public_issues_manifest_required_fields" python3 - "$PUBLIC_ISSUES_MANIFEST" <<'PY' || fail "public_issues_manifest_required_fields"
@@ -296,6 +310,10 @@ if issue_011.get("github_issue_url") != "https://github.com/igor04091968/AWatch-
raise SystemExit("issue 011 URL mismatch") raise SystemExit("issue 011 URL mismatch")
if issue_011.get("next_evidence_doc") != "docs/PR_REVIEW_EVIDENCE_RU.md": if issue_011.get("next_evidence_doc") != "docs/PR_REVIEW_EVIDENCE_RU.md":
raise SystemExit("issue 011 next_evidence_doc mismatch") raise SystemExit("issue 011 next_evidence_doc mismatch")
if issue_011.get("evidence_status") != "pending_review_required":
raise SystemExit("issue 011 evidence_status mismatch")
if issue_011.get("evidence_doc") != "docs/PR_REVIEW_EVIDENCE_RU.md":
raise SystemExit("issue 011 evidence_doc mismatch")
issue_012 = issue_by_id.get("012") or {} issue_012 = issue_by_id.get("012") or {}
if issue_012.get("status") != "created": if issue_012.get("status") != "created":
@@ -304,6 +322,10 @@ if issue_012.get("github_issue_url") != "https://github.com/igor04091968/AWatch-
raise SystemExit("issue 012 URL mismatch") raise SystemExit("issue 012 URL mismatch")
if issue_012.get("next_evidence_doc") != "docs/BRANCH_PROTECTION_EVIDENCE_RU.md": if issue_012.get("next_evidence_doc") != "docs/BRANCH_PROTECTION_EVIDENCE_RU.md":
raise SystemExit("issue 012 next_evidence_doc mismatch") raise SystemExit("issue 012 next_evidence_doc mismatch")
if issue_012.get("evidence_status") != "verified_active_ruleset":
raise SystemExit("issue 012 evidence_status mismatch")
if issue_012.get("evidence_doc") != "docs/BRANCH_PROTECTION_EVIDENCE_RU.md":
raise SystemExit("issue 012 evidence_doc mismatch")
PY PY
else else
fail "json_validator_missing:jq_or_python3_required" fail "json_validator_missing:jq_or_python3_required"
@@ -340,8 +362,17 @@ require_grep "PUBLIC_ISSUES_CREATION_RUNBOOK_RU\\.md" "docs/PROJECT_STATUS_RU.md
require_grep "Public issues:[[:space:]]*created and linked in manifest|Созданы 12 публичных" "docs/PROJECT_STATUS_RU.md" "project_status_issue_creation_created_urls" require_grep "Public issues:[[:space:]]*created and linked in manifest|Созданы 12 публичных" "docs/PROJECT_STATUS_RU.md" "project_status_issue_creation_created_urls"
require_grep "PR-based workflow documentation:[[:space:]]*ready" "docs/PROJECT_STATUS_RU.md" "project_status_pr_workflow_ready" require_grep "PR-based workflow documentation:[[:space:]]*ready" "docs/PROJECT_STATUS_RU.md" "project_status_pr_workflow_ready"
require_grep "Branch protection evidence package:[[:space:]]*ready" "docs/PROJECT_STATUS_RU.md" "project_status_branch_evidence_ready" require_grep "Branch protection evidence package:[[:space:]]*ready" "docs/PROJECT_STATUS_RU.md" "project_status_branch_evidence_ready"
require_grep "pending_manual_verification" "docs/PROJECT_STATUS_RU.md" "project_status_branch_pending_manual" require_grep 'Branch protection ruleset:[[:space:]]*`verified_active_ruleset`' "docs/PROJECT_STATUS_RU.md" "project_status_branch_verified_ruleset"
require_grep 'Branch protection target branch:[[:space:]]*`main`' "docs/PROJECT_STATUS_RU.md" "project_status_branch_target_main"
require_grep "Coverage baseline" "docs/PROJECT_STATUS_RU.md" "project_status_branch_check_coverage_baseline"
require_grep "security" "docs/PROJECT_STATUS_RU.md" "project_status_branch_check_security"
require_grep "rust-checks" "docs/PROJECT_STATUS_RU.md" "project_status_branch_check_rust_checks"
require_grep "docs-registry-checks" "docs/PROJECT_STATUS_RU.md" "project_status_branch_check_docs_registry_checks"
require_grep "smoke-checks" "docs/PROJECT_STATUS_RU.md" "project_status_branch_check_smoke_checks"
require_grep "First reviewed PR evidence:[[:space:]]*pending|First reviewed PR evidence remains pending" "docs/PROJECT_STATUS_RU.md" "project_status_first_reviewed_pr_pending" require_grep "First reviewed PR evidence:[[:space:]]*pending|First reviewed PR evidence remains pending" "docs/PROJECT_STATUS_RU.md" "project_status_first_reviewed_pr_pending"
require_grep "First protected PR workflow: PR #50 opened" "docs/PROJECT_STATUS_RU.md" "project_status_first_protected_pr"
require_grep "pending_review_required" "docs/PROJECT_STATUS_RU.md" "project_status_pr_pending_review_required"
require_grep "External peer review remains pending" "docs/PROJECT_STATUS_RU.md" "project_status_external_peer_review_pending"
require_grep "GITEA_BACKUP_AND_RESTORE_RUNBOOK_RU\\.md|Restore outline|Post-restore checks" "docs/registry/GITEA_BACKUP_AND_RESTORE_RUNBOOK_RU.md" "backup_restore_runbook" require_grep "GITEA_BACKUP_AND_RESTORE_RUNBOOK_RU\\.md|Restore outline|Post-restore checks" "docs/registry/GITEA_BACKUP_AND_RESTORE_RUNBOOK_RU.md" "backup_restore_runbook"
require_grep "awatch-gitea-backup\\.timer" "docs/registry/GITEA_BACKUP_AND_RESTORE_RUNBOOK_RU.md" "backup_timer" require_grep "awatch-gitea-backup\\.timer" "docs/registry/GITEA_BACKUP_AND_RESTORE_RUNBOOK_RU.md" "backup_timer"
require_grep "sha256|SHA256" "docs/registry/GITEA_BACKUP_AND_RESTORE_RUNBOOK_RU.md" "backup_sha256" require_grep "sha256|SHA256" "docs/registry/GITEA_BACKUP_AND_RESTORE_RUNBOOK_RU.md" "backup_sha256"
@@ -374,6 +405,11 @@ require_grep "docs/public-issues" "docs/registry/REGISTRY_READINESS_CHANGELOG_RU
require_grep "runtime/product code changes" "docs/registry/REGISTRY_READINESS_CHANGELOG_RU.md" "changelog_public_issues_no_runtime" require_grep "runtime/product code changes" "docs/registry/REGISTRY_READINESS_CHANGELOG_RU.md" "changelog_public_issues_no_runtime"
require_grep "GitHub remains public mirror validation only" "docs/registry/REGISTRY_READINESS_CHANGELOG_RU.md" "changelog_public_issues_github_role" require_grep "GitHub remains public mirror validation only" "docs/registry/REGISTRY_READINESS_CHANGELOG_RU.md" "changelog_public_issues_github_role"
require_grep "branch protection and PR review evidence package" "docs/registry/REGISTRY_READINESS_CHANGELOG_RU.md" "changelog_branch_pr_evidence_package" require_grep "branch protection and PR review evidence package" "docs/registry/REGISTRY_READINESS_CHANGELOG_RU.md" "changelog_branch_pr_evidence_package"
require_grep "verified GitHub ruleset evidence" "docs/registry/REGISTRY_READINESS_CHANGELOG_RU.md" "changelog_verified_ruleset_evidence"
require_grep "protected PR workflow evidence recorded" "docs/registry/REGISTRY_READINESS_CHANGELOG_RU.md" "changelog_protected_pr_workflow"
require_grep "PR #50" "docs/registry/REGISTRY_READINESS_CHANGELOG_RU.md" "changelog_pr_50"
require_grep "pending_review_required" "docs/registry/REGISTRY_READINESS_CHANGELOG_RU.md" "changelog_pr_pending_review_required"
require_grep "This is not registry release evidence" "docs/registry/REGISTRY_READINESS_CHANGELOG_RU.md" "changelog_ruleset_not_registry_evidence"
require_grep "No runtime/product code changes" "docs/registry/REGISTRY_READINESS_CHANGELOG_RU.md" "changelog_branch_pr_no_runtime" require_grep "No runtime/product code changes" "docs/registry/REGISTRY_READINESS_CHANGELOG_RU.md" "changelog_branch_pr_no_runtime"
require_grep "External peer review is not claimed completed" "docs/registry/REGISTRY_READINESS_CHANGELOG_RU.md" "changelog_external_review_not_completed" require_grep "External peer review is not claimed completed" "docs/registry/REGISTRY_READINESS_CHANGELOG_RU.md" "changelog_external_review_not_completed"
require_grep "Restore test is not claimed as completed" "docs/registry/REGISTRY_READINESS_CHANGELOG_RU.md" "changelog_restore_not_completed" require_grep "Restore test is not claimed as completed" "docs/registry/REGISTRY_READINESS_CHANGELOG_RU.md" "changelog_restore_not_completed"
@@ -422,14 +458,14 @@ require_grep 'Required approvals:[[:space:]]*`1`' "docs/BRANCH_PROTECTION_POLICY
require_grep "Dismiss stale approvals" "docs/BRANCH_PROTECTION_POLICY_RU.md" "branch_policy_dismiss_stale" require_grep "Dismiss stale approvals" "docs/BRANCH_PROTECTION_POLICY_RU.md" "branch_policy_dismiss_stale"
require_grep "CODEOWNERS" "docs/BRANCH_PROTECTION_POLICY_RU.md" "branch_policy_codeowners_review" require_grep "CODEOWNERS" "docs/BRANCH_PROTECTION_POLICY_RU.md" "branch_policy_codeowners_review"
require_grep "Require status checks" "docs/BRANCH_PROTECTION_POLICY_RU.md" "branch_policy_status_checks" require_grep "Require status checks" "docs/BRANCH_PROTECTION_POLICY_RU.md" "branch_policy_status_checks"
require_grep "CI / Rust checks" "docs/BRANCH_PROTECTION_POLICY_RU.md" "branch_policy_ci_rust_checks" require_grep 'Ruleset name:[[:space:]]*`main`' "docs/BRANCH_PROTECTION_POLICY_RU.md" "branch_policy_ruleset_main"
require_grep "CI / Docs and registry checks" "docs/BRANCH_PROTECTION_POLICY_RU.md" "branch_policy_ci_docs_registry_checks" require_grep 'Enforcement:[[:space:]]*`active`' "docs/BRANCH_PROTECTION_POLICY_RU.md" "branch_policy_enforcement_active"
require_grep "CI / Smoke checks" "docs/BRANCH_PROTECTION_POLICY_RU.md" "branch_policy_ci_smoke_checks" require_grep "Bypass list:[[:space:]]*empty" "docs/BRANCH_PROTECTION_POLICY_RU.md" "branch_policy_bypass_empty"
require_grep "Coverage / Coverage baseline" "docs/BRANCH_PROTECTION_POLICY_RU.md" "branch_policy_coverage_baseline_check" require_grep "Coverage baseline" "docs/BRANCH_PROTECTION_POLICY_RU.md" "branch_policy_coverage_baseline_check"
require_grep "Security / Cargo audit" "docs/BRANCH_PROTECTION_POLICY_RU.md" "branch_policy_security_cargo_audit" require_grep "security" "docs/BRANCH_PROTECTION_POLICY_RU.md" "branch_policy_security_check"
require_grep "Security / Cargo deny" "docs/BRANCH_PROTECTION_POLICY_RU.md" "branch_policy_security_cargo_deny" require_grep "rust-checks" "docs/BRANCH_PROTECTION_POLICY_RU.md" "branch_policy_rust_checks"
require_grep "Security / Secret pattern check" "docs/BRANCH_PROTECTION_POLICY_RU.md" "branch_policy_secret_pattern_check" require_grep "docs-registry-checks" "docs/BRANCH_PROTECTION_POLICY_RU.md" "branch_policy_docs_registry_checks"
require_grep "Security / Dependency review" "docs/BRANCH_PROTECTION_POLICY_RU.md" "branch_policy_dependency_review" require_grep "smoke-checks" "docs/BRANCH_PROTECTION_POLICY_RU.md" "branch_policy_smoke_checks"
require_grep 'Require `CI` workflow' "docs/BRANCH_PROTECTION_POLICY_RU.md" "branch_policy_ci" require_grep 'Require `CI` workflow' "docs/BRANCH_PROTECTION_POLICY_RU.md" "branch_policy_ci"
require_grep 'Require `Security` workflow' "docs/BRANCH_PROTECTION_POLICY_RU.md" "branch_policy_security" require_grep 'Require `Security` workflow' "docs/BRANCH_PROTECTION_POLICY_RU.md" "branch_policy_security"
require_grep 'Require `Coverage` workflow' "docs/BRANCH_PROTECTION_POLICY_RU.md" "branch_policy_coverage" require_grep 'Require `Coverage` workflow' "docs/BRANCH_PROTECTION_POLICY_RU.md" "branch_policy_coverage"
@@ -438,12 +474,21 @@ require_grep "Restrict force push" "docs/BRANCH_PROTECTION_POLICY_RU.md" "branch
require_grep "Require conversation resolution" "docs/BRANCH_PROTECTION_POLICY_RU.md" "branch_policy_conversation_resolution" require_grep "Require conversation resolution" "docs/BRANCH_PROTECTION_POLICY_RU.md" "branch_policy_conversation_resolution"
require_grep "Require linear history" "docs/BRANCH_PROTECTION_POLICY_RU.md" "branch_policy_linear_history" require_grep "Require linear history" "docs/BRANCH_PROTECTION_POLICY_RU.md" "branch_policy_linear_history"
require_grep "emergency-only" "docs/BRANCH_PROTECTION_POLICY_RU.md" "branch_policy_admin_bypass" require_grep "emergency-only" "docs/BRANCH_PROTECTION_POLICY_RU.md" "branch_policy_admin_bypass"
require_grep "recommended policy|advisory branch protection policy" "docs/BRANCH_PROTECTION_POLICY_RU.md" "branch_policy_advisory_not_enabled" require_grep "verified_active_ruleset" "docs/BRANCH_PROTECTION_POLICY_RU.md" "branch_policy_verified_active_ruleset"
require_grep "branch_protection_status:[[:space:]]*\"pending_manual_verification\"" "docs/BRANCH_PROTECTION_EVIDENCE_RU.md" "branch_evidence_pending_manual" require_grep "branch_protection_status:[[:space:]]*\"verified_active_ruleset\"" "docs/BRANCH_PROTECTION_EVIDENCE_RU.md" "branch_evidence_verified_active"
require_grep "https://github\\.com/igor04091968/AWatch-rus/issues/49" "docs/BRANCH_PROTECTION_EVIDENCE_RU.md" "branch_evidence_issue_49" require_grep "https://github\\.com/igor04091968/AWatch-rus/issues/49" "docs/BRANCH_PROTECTION_EVIDENCE_RU.md" "branch_evidence_issue_49"
require_grep "Not Registry Release Evidence|Not registry release evidence" "docs/BRANCH_PROTECTION_EVIDENCE_RU.md" "branch_evidence_not_registry" require_grep "Not Registry Release Evidence|Not registry release evidence" "docs/BRANCH_PROTECTION_EVIDENCE_RU.md" "branch_evidence_not_registry"
require_grep "Russian Contour Note" "docs/BRANCH_PROTECTION_EVIDENCE_RU.md" "branch_evidence_russian_contour" require_grep "Russian Contour Note" "docs/BRANCH_PROTECTION_EVIDENCE_RU.md" "branch_evidence_russian_contour"
require_grep "CI / Rust checks" "docs/BRANCH_PROTECTION_EVIDENCE_RU.md" "branch_evidence_required_checks" require_grep 'enforcement:[[:space:]]*`active`' "docs/BRANCH_PROTECTION_EVIDENCE_RU.md" "branch_evidence_enforcement_active"
require_grep 'bypass_list:[[:space:]]*`empty`' "docs/BRANCH_PROTECTION_EVIDENCE_RU.md" "branch_evidence_bypass_empty"
require_grep 'required approvals:[[:space:]]*`1`' "docs/BRANCH_PROTECTION_EVIDENCE_RU.md" "branch_evidence_required_approvals"
require_grep 'require review from Code Owners:[[:space:]]*`enabled`' "docs/BRANCH_PROTECTION_EVIDENCE_RU.md" "branch_evidence_codeowners_enabled"
require_grep "Coverage baseline" "docs/BRANCH_PROTECTION_EVIDENCE_RU.md" "branch_evidence_coverage_baseline"
require_grep "security" "docs/BRANCH_PROTECTION_EVIDENCE_RU.md" "branch_evidence_security_check"
require_grep "rust-checks" "docs/BRANCH_PROTECTION_EVIDENCE_RU.md" "branch_evidence_rust_checks"
require_grep "docs-registry-checks" "docs/BRANCH_PROTECTION_EVIDENCE_RU.md" "branch_evidence_docs_registry_checks"
require_grep "smoke-checks" "docs/BRANCH_PROTECTION_EVIDENCE_RU.md" "branch_evidence_smoke_checks"
require_grep "GitHub ruleset / branch protection evidence is public governance evidence only" "docs/BRANCH_PROTECTION_EVIDENCE_RU.md" "branch_evidence_governance_only"
require_grep "workflow documentation ready" "docs/PR_REVIEW_WORKFLOW_RU.md" "pr_workflow_ready" require_grep "workflow documentation ready" "docs/PR_REVIEW_WORKFLOW_RU.md" "pr_workflow_ready"
require_grep "https://github\\.com/igor04091968/AWatch-rus/issues/48" "docs/PR_REVIEW_WORKFLOW_RU.md" "pr_workflow_issue_48" require_grep "https://github\\.com/igor04091968/AWatch-rus/issues/48" "docs/PR_REVIEW_WORKFLOW_RU.md" "pr_workflow_issue_48"
require_grep "PR template must be completed" "docs/PR_REVIEW_WORKFLOW_RU.md" "pr_workflow_template_required" require_grep "PR template must be completed" "docs/PR_REVIEW_WORKFLOW_RU.md" "pr_workflow_template_required"
@@ -451,12 +496,26 @@ require_grep "CODEOWNERS" "docs/PR_REVIEW_WORKFLOW_RU.md" "pr_workflow_codeowner
require_grep "CI, Coverage and Security checks" "docs/PR_REVIEW_WORKFLOW_RU.md" "pr_workflow_checks" require_grep "CI, Coverage and Security checks" "docs/PR_REVIEW_WORKFLOW_RU.md" "pr_workflow_checks"
require_grep "public secret-pattern scan" "docs/PR_REVIEW_WORKFLOW_RU.md" "pr_workflow_secret_scan" require_grep "public secret-pattern scan" "docs/PR_REVIEW_WORKFLOW_RU.md" "pr_workflow_secret_scan"
require_grep "GitHub Actions is public mirror validation only" "docs/PR_REVIEW_WORKFLOW_RU.md" "pr_workflow_github_public_only" require_grep "GitHub Actions is public mirror validation only" "docs/PR_REVIEW_WORKFLOW_RU.md" "pr_workflow_github_public_only"
require_grep "pr_review_evidence_status:[[:space:]]*\"pending_until_first_reviewed_pr_is_merged\"" "docs/PR_REVIEW_EVIDENCE_RU.md" "pr_evidence_pending_first_reviewed_pr" require_grep "pr_review_status:[[:space:]]*\"pending_review_required\"" "docs/PR_REVIEW_EVIDENCE_RU.md" "pr_evidence_pending_review_required"
require_grep "https://github\\.com/igor04091968/AWatch-rus/pull/50" "docs/PR_REVIEW_EVIDENCE_RU.md" "pr_evidence_pr_50"
require_grep 'Required checks status:[[:space:]]*`passed`' "docs/PR_REVIEW_EVIDENCE_RU.md" "pr_evidence_required_checks_passed"
require_grep 'Review requirement status:[[:space:]]*`pending_review_required`' "docs/PR_REVIEW_EVIDENCE_RU.md" "pr_evidence_review_pending"
require_grep 'Merge status:[[:space:]]*`open`' "docs/PR_REVIEW_EVIDENCE_RU.md" "pr_evidence_merge_open"
require_grep 'Admin bypass used:[[:space:]]*`false`' "docs/PR_REVIEW_EVIDENCE_RU.md" "pr_evidence_no_bypass"
require_grep "Coverage baseline" "docs/PR_REVIEW_EVIDENCE_RU.md" "pr_evidence_coverage_baseline"
require_grep "security" "docs/PR_REVIEW_EVIDENCE_RU.md" "pr_evidence_security"
require_grep "rust-checks" "docs/PR_REVIEW_EVIDENCE_RU.md" "pr_evidence_rust_checks"
require_grep "docs-registry-checks" "docs/PR_REVIEW_EVIDENCE_RU.md" "pr_evidence_docs_registry_checks"
require_grep "smoke-checks" "docs/PR_REVIEW_EVIDENCE_RU.md" "pr_evidence_smoke_checks"
require_grep "reviewer approval" "docs/PR_REVIEW_EVIDENCE_RU.md" "pr_evidence_reviewer_approval" require_grep "reviewer approval" "docs/PR_REVIEW_EVIDENCE_RU.md" "pr_evidence_reviewer_approval"
require_grep "External peer review completed:[[:space:]]*not claimed" "docs/PR_REVIEW_EVIDENCE_RU.md" "pr_evidence_external_not_claimed" require_grep "External peer review completed:[[:space:]]*not claimed" "docs/PR_REVIEW_EVIDENCE_RU.md" "pr_evidence_external_not_claimed"
require_grep "Not Registry Release Evidence|Not registry release evidence" "docs/PR_REVIEW_EVIDENCE_RU.md" "pr_evidence_not_registry" require_grep "Not Registry Release Evidence|Not registry release evidence" "docs/PR_REVIEW_EVIDENCE_RU.md" "pr_evidence_not_registry"
require_grep "\"next_evidence_doc\"[[:space:]]*:[[:space:]]*\"docs/PR_REVIEW_EVIDENCE_RU\\.md\"" "docs/public-issues/public-issues-manifest.json" "manifest_issue_48_next_evidence_doc" require_grep "\"next_evidence_doc\"[[:space:]]*:[[:space:]]*\"docs/PR_REVIEW_EVIDENCE_RU\\.md\"" "docs/public-issues/public-issues-manifest.json" "manifest_issue_48_next_evidence_doc"
require_grep "\"evidence_status\"[[:space:]]*:[[:space:]]*\"pending_review_required\"" "docs/public-issues/public-issues-manifest.json" "manifest_issue_48_evidence_status"
require_grep "\"evidence_doc\"[[:space:]]*:[[:space:]]*\"docs/PR_REVIEW_EVIDENCE_RU\\.md\"" "docs/public-issues/public-issues-manifest.json" "manifest_issue_48_evidence_doc"
require_grep "\"next_evidence_doc\"[[:space:]]*:[[:space:]]*\"docs/BRANCH_PROTECTION_EVIDENCE_RU\\.md\"" "docs/public-issues/public-issues-manifest.json" "manifest_issue_49_next_evidence_doc" require_grep "\"next_evidence_doc\"[[:space:]]*:[[:space:]]*\"docs/BRANCH_PROTECTION_EVIDENCE_RU\\.md\"" "docs/public-issues/public-issues-manifest.json" "manifest_issue_49_next_evidence_doc"
require_grep "\"evidence_status\"[[:space:]]*:[[:space:]]*\"verified_active_ruleset\"" "docs/public-issues/public-issues-manifest.json" "manifest_issue_49_evidence_status"
require_grep "\"evidence_doc\"[[:space:]]*:[[:space:]]*\"docs/BRANCH_PROTECTION_EVIDENCE_RU\\.md\"" "docs/public-issues/public-issues-manifest.json" "manifest_issue_49_evidence_doc"
require_grep "Один основной разработчик" "docs/RESIDUAL_RISKS_RU.md" "risk_single_developer" require_grep "Один основной разработчик" "docs/RESIDUAL_RISKS_RU.md" "risk_single_developer"
require_grep "Нет внешнего visible peer review" "docs/RESIDUAL_RISKS_RU.md" "risk_peer_review" require_grep "Нет внешнего visible peer review" "docs/RESIDUAL_RISKS_RU.md" "risk_peer_review"
require_grep "Низкая публичная активность issue tracker" "docs/RESIDUAL_RISKS_RU.md" "risk_issue_tracker_activity" require_grep "Низкая публичная активность issue tracker" "docs/RESIDUAL_RISKS_RU.md" "risk_issue_tracker_activity"
@@ -599,12 +658,34 @@ fi
rm -f /tmp/registry_forbidden_registry_submission_done.$$ rm -f /tmp/registry_forbidden_registry_submission_done.$$
if grep -RInEi "(branch protection).{0,120}(enabled|включ(е|ё)н|настроен|active)" "$ROOT/README.md" "$ROOT/docs/PROJECT_STATUS_RU.md" "$ROOT/docs/BRANCH_PROTECTION_POLICY_RU.md" "$ROOT/docs/BRANCH_PROTECTION_EVIDENCE_RU.md" "$ROOT/docs/PR_REVIEW_WORKFLOW_RU.md" "$ROOT/docs/PR_REVIEW_EVIDENCE_RU.md" "$REGISTRY_DIR"/*.md \ if grep -RInEi "(branch protection).{0,120}(enabled|включ(е|ё)н|настроен|active)" "$ROOT/README.md" "$ROOT/docs/PROJECT_STATUS_RU.md" "$ROOT/docs/BRANCH_PROTECTION_POLICY_RU.md" "$ROOT/docs/BRANCH_PROTECTION_EVIDENCE_RU.md" "$ROOT/docs/PR_REVIEW_WORKFLOW_RU.md" "$ROOT/docs/PR_REVIEW_EVIDENCE_RU.md" "$REGISTRY_DIR"/*.md \
| grep -Eiv "(not |не |не утверждает|not claimed|advisory|until repository settings|если применимо|recommended|pending_manual_verification|until maintainer evidence|until.*evidence|to verify)" \ | grep -Eiv "(not |не |не утверждает|not claimed|no claim|no assertion|advisory|until repository settings|если применимо|recommended|pending|pending_manual_verification|verified_active_ruleset|maintainer-verified|verified active by maintainer|verified GitHub ruleset|until maintainer evidence|until.*evidence|to verify)" \
>/tmp/registry_forbidden_branch_protection_enabled.$$ 2>/dev/null; then >/tmp/registry_forbidden_branch_protection_enabled.$$ 2>/dev/null; then
fail "forbidden_claim_branch_protection_enabled:$(cat /tmp/registry_forbidden_branch_protection_enabled.$$)" fail "forbidden_claim_branch_protection_enabled:$(cat /tmp/registry_forbidden_branch_protection_enabled.$$)"
fi fi
rm -f /tmp/registry_forbidden_branch_protection_enabled.$$ rm -f /tmp/registry_forbidden_branch_protection_enabled.$$
if grep -Eq 'pr_review_status:[[:space:]]*"verified"' "$ROOT/docs/PR_REVIEW_EVIDENCE_RU.md" \
&& ! grep -Eq 'Admin bypass used:[[:space:]]*`false`' "$ROOT/docs/PR_REVIEW_EVIDENCE_RU.md"; then
fail "pr_review_verified_requires_admin_bypass_false"
fi
if grep -Eq 'Admin bypass used:[[:space:]]*`true`' "$ROOT/docs/PR_REVIEW_EVIDENCE_RU.md" \
&& grep -Eiq "first PR review workflow:[[:space:]]*verified|PR review workflow:[[:space:]]*verified|first reviewed PR evidence:[[:space:]]*verified" "$ROOT/docs/PROJECT_STATUS_RU.md"; then
fail "bypass_pr_must_not_mark_review_workflow_verified"
fi
if grep -RInEi "(CodeQL|code scanning).{0,80}(enabled|required|requirement|tool)" \
"$ROOT/docs/BRANCH_PROTECTION_EVIDENCE_RU.md" \
"$ROOT/docs/BRANCH_PROTECTION_POLICY_RU.md" \
"$ROOT/docs/PROJECT_STATUS_RU.md" \
"$ROOT/docs/RESIDUAL_RISKS_RU.md" \
"$ROOT/docs/PR_REVIEW_EVIDENCE_RU.md" \
"$REGISTRY_DIR/REGISTRY_READINESS_CHANGELOG_RU.md" \
>/tmp/registry_forbidden_codeql_claim.$$ 2>/dev/null; then
fail "forbidden_claim_codeql_enabled_or_required:$(cat /tmp/registry_forbidden_codeql_claim.$$)"
fi
rm -f /tmp/registry_forbidden_codeql_claim.$$
if grep -RInEi "(external|visible|peer).{0,80}(review).{0,120}(active|performed|completed|done|выполняется|проведен|провед(е|ё)н|активен)" "$ROOT/README.md" "$ROOT/docs/PROJECT_STATUS_RU.md" "$ROOT/docs/RESIDUAL_RISKS_RU.md" "$ROOT/docs/PR_REVIEW_WORKFLOW_RU.md" "$ROOT/docs/PR_REVIEW_EVIDENCE_RU.md" "$REGISTRY_DIR"/*.md \ if grep -RInEi "(external|visible|peer).{0,80}(review).{0,120}(active|performed|completed|done|выполняется|проведен|провед(е|ё)н|активен)" "$ROOT/README.md" "$ROOT/docs/PROJECT_STATUS_RU.md" "$ROOT/docs/RESIDUAL_RISKS_RU.md" "$ROOT/docs/PR_REVIEW_WORKFLOW_RU.md" "$ROOT/docs/PR_REVIEW_EVIDENCE_RU.md" "$REGISTRY_DIR"/*.md \
| grep -Eiv "(not |не |pending|still pending|не утверждает|not claimed|until public reviewed PRs|until reviewed PR evidence|unless a reviewed public PR|is not claimed)" \ | grep -Eiv "(not |не |pending|still pending|не утверждает|not claimed|until public reviewed PRs|until reviewed PR evidence|unless a reviewed public PR|is not claimed)" \
>/tmp/registry_forbidden_external_review_active.$$ 2>/dev/null; then >/tmp/registry_forbidden_external_review_active.$$ 2>/dev/null; then