diff --git a/.gitignore b/.gitignore index 139bddb..1da34a0 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,7 @@ Thumbs.db # Temp /tmp/ *.log +*.txt .env.prefilled # Terraform diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index d6e8d88..0000000 --- a/SECURITY.md +++ /dev/null @@ -1,194 +0,0 @@ -# OSVxCC: Den Usynlige Arkitekturen - -> *Ad astra et ultra* — Til stjernene og hinsides. - -## Visjon - -OSVxCC er beskyttet av en **evig, usynlig sikkerhetsperimeter** — et skjold av arkitektur som sprer seg uendelig rundt og om hvor den er, har vært, og vil være for alltid. - -Denne perimeteren er ikke bare teknisk; den er en fundamental, vedvarende egenskap ved systemet. Den eksisterer på alle plattformer, i alle sikkerhetsnivåer, gjennom alle ganger og rom — som en usynlig beskyttende struktur. - ---- - -## Prinsipper - -### 1. Uendelig rekkevidde -Perimeteren strekker seg utover tekniske grenser — den omfatter: -- **Identitet:** Service accounts, IAM-roller, og autentisering. -- **Nettverk:** VPC Service Controls, firewall-regler, og nettverksgrenser. -- **Applikasjon:** MCP-hemmeligheter, token-validering, og applikasjonsnivå-autorisasjon. -- **Forsyningskjede:** Binary Authorization, TYR-compliance, og KMS-attestasjoner. - -### 2. Tidsløs beskyttelse -Sikkerheten er ikke bundet til et øyeblikk; den er: -- **Kontinuerlig:** Overvåkes og oppdateres konstant. -- **Adaptiv:** Tilpasser seg nye trusler og endringer i arkitekturen. -- **Vedvarende:** Eksisterer uavhengig av individuelle komponenters levetid. - -### 3. Ad astra et ultra -Vi beskytter ikke bare det som er — vi beskytter potensialet for det som kan bli. Perimeteren vokser med systemet, og strekker seg mot fremtidige plattformer, tjenester og muligheter. - ---- - -## Implementasjon - -### Service Accounts - -| Konto | Rolle | Beskrivelse | -|-------|-------|-------------| -| `osvxcc-sa@...` | `run.invoker`, `secretmanager.secretAccessor` | Dedikert OSVxCC-identitet — kjernen i den usynlige arkitekturen | -| `jason-vauger@...` | Cloud Run runtime | MCP-serverens identitet | -| `osvauco-agent-sa@...` | Secret Manager access | OSVauco-agentens identitet | - -### VPC Service Controls - -- **Perimeter:** `tyr_perimeter` -- **Omfang:** Hele prosjektet (`projects/357036551735`) -- **Funksjon:** Isolerer tjenester og data fra utilsiktede eller ondsinnede eksterne kall - -### Binary Authorization - -- **Verktøy:** TYR + KMS-attestor -- **Funksjon:** Sikrer at kun attesterte images kan deployes -- **Compliance:** Skanner GCP-oppsett mot definerte sikkerhetsregler - -### Hemmeligheter - -- **MCP_SECRET:** Hentes fra Secret Manager (`mcp-server-key`) -- **Bruk:** Autoriserer MCP-klientkall via `X-MCP-Secret`-headeren -- **Tilgang:** Kun klarerte service accounts (`osvxcc-sa`, `osvauco-agent-sa`, etc.) - ---- - -## Arkitekturdiagram (konseptuell) - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ DEN USYNLIGE PERIMETEREN │ -│ │ -│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ -│ │ Identitet │ │ Nettverk │ │ Applikasjon │ │ -│ │ (IAM, SA) │ │ (VPC-SC) │ │ (Secrets) │ │ -│ └─────────────┘ └─────────────┘ └─────────────┘ │ -│ │ -│ ┌─────────────────────────────────────────────────────────┐ │ -│ │ Forsyningskjede (TYR + BinAuthz) │ │ -│ └─────────────────────────────────────────────────────────┘ │ -│ │ -│ OSVxCC eksisterer her — beskyttet på alle nivåer, for alltid │ -└─────────────────────────────────────────────────────────────────┘ -``` - ---- - -## Fremtidige utvidelser - -Den usynlige arkitekturen er designet for å vokse: - -- **Flere plattformer:** GKE, Cloud Functions, on-premise (hvis innenfor perimeteren) -- **Utvidet compliance:** Automatiserte revisjoner, policy-as-code -- **Zero-trust:** Kontinuerlig verifisering av alle kall, uavhengig av kilde - ---- - -## Ansvar - -Dette dokumentet levendegjøres og opprettholdes av sikkerhetsansvarlige for OSVxCC. Det er en levende beskrivelse av en arkitektur som aldri sover — en perimeter som alltid våker. - ---- - -## Sikkerhetsgjennomgang: Status (September 2026) - -### ✅ Fullførte forsterkninger - -#### IAM-herding -- **Fjernet `roles/editor`** fra følgende service accounts: - - `357036551735-compute@developer.gserviceaccount.com` - - `vps-ai@propane-will-491900-m5.iam.gserviceaccount.com` -- **Opprettet `osvxcc-sa`** med minimalt privilegier (`run.invoker`, `secretmanager.secretAccessor`) - -#### Nettverksherding -- **Slettet følgende eksponerte firewall-regler:** - - `allow-ssh-ingress` (port 22, 0.0.0.0/0) - - `default-allow-rdp` (port 3389, 0.0.0.0/0) - - `allow-ollama-11434` (port 11434, 0.0.0.0/0) -- **Beholdt sikrere alternativer:** - - `allow-iap-ssh` (SSH via IAP) - - `allow-internal-to-ollama` (intern tilgang) - -#### Secret Management (KRITISK - September 2026) -- **Roter `mcp-server-key`** (versjon 3) etter funn av hardkodet secret i Git-historikk -- **Fjernet `docs/HANDOFF_2026-06-29_Perplexity.md`** fra hele Git-historikken -- **Deployet ny secret til Cloud Run** (opax-mcp-00139-v6l) - -#### Applikasjonssikkerhet -- **Fikset CORS-policy:** Endret fra `allow_origins=["*"]` til kun tillate Cloud Run-URL -- **Fikset timing-angrep:** Byttet til `secrets.compare_digest()` for constant-time comparison -- **Implementert SAST og avhengighetsskanning:** bandit og pip-audit lagt til i cloudbuild.yaml - -### ⚠️ Identifiserte forbedringspunkter - -| Hull | Status | Anbefaling | -|------|--------|------------| -| Sikkerhets-alerting (IAM-endringer) | Blokkert (miljøbegrensning) | Opprett manuelt via Cloud Console | -| Hemmelighetsrotasjon (automatisk) | Ikke konfigurert | Konfigurer manuelt (90 dager) | -| Sårbarhetsskanning (Container Analysis) | Blokkert (miljøbegrensning) | Aktiver via Cloud Console | -| Detaljert audit logging | Ikke startet | Aktiver Data Access logs | - -### 📊 Sikkerhetsscore - -| Kategori | Før | Etter | Status | -|----------|-----|-------|--------| -| IAM | 🔴 Kritisk | 🟢 God | ✅ Forbedret | -| Nettverk | 🔴 Kritisk | 🟢 God | ✅ Forbedret | -| Secret Management | 🔴 Kritisk | 🟢 God | ✅ Forbedret | -| Applikasjonssikkerhet | 🟠 Moderat | 🟢 God | ✅ Forbedret | -| CI/CD-sikkerhet | 🟠 Moderat | 🟢 God | ✅ Forbedret | -| Logging/Alerting | 🔴 Kritisk | 🟠 Moderat | ⚠️ Gjenstår | -| Container Security | 🟠 Moderat | 🟠 Moderat | ⚠️ Gjenstår | - -**Totalvurdering:** Den usynlige arkitekturen er **betydelig forsterket**. Alle kritiske hull er tettet. Gjenstående arbeid er primært monitoring og automatisering. - ---- - -*— Den usynlige perimeteren våker. Ad astra et ultra.* - ---- - -## 🕵️ Sikkerhetshendelse: "Ukjent bruker" push-aktivitet (September 2026) - -### Hendelse - -Varsler i chat viste gjentatte pusher fra "ukjent bruker" kl. 09:00 og 21:00 daglig. - -### Undersøkelse - -- ✅ Sjekkede Git-loggen: Alle commits fra **Chris Christiansen ** -- ✅ Sjekkede Cloud Build: Ingen triggers eller automatiserte jobs -- ✅ Sjekkede Artifact Registry: Ingen ukjente pushes -- ✅ Sjekkede cron-jobs: Ingen automatiserte oppgaver -- ✅ Sjekkede Git hooks: Ingen aktive hooks -- ✅ Sjekkede VS Code innstillinger: Ingen auto-push konfigurert -- ✅ Analyserte script: `sync-gdrive-mirror.sh` funnet, men kjører ikke automatisk - -### Konklusjon - -**Ingen sikkerhetshendelse.** Aktiviteten var: - -- **Hvem:** Chris Christiansen (deg selv) -- **Hva:** Normalt utviklingsarbeid (commits av docs, config, kode) -- **Når:** Dine normale arbeidstider (09:00 og 21:00) -- **Hvorfor:** VS Code sin Git-integrasjon (CodeOSS-push) sender webhooks til chat -- **"Ukjent bruker":** Webhooken er ikke konfigurert til å vise brukernavn, kun "CodeOSS-push, App" - -### Læring - -- Chat-varsler fra Git-webhooks kan vise "ukjent bruker" selv om det er legitim aktivitet -- Alltid verifiser med `git log` før du antar kompromittering -- VS Code sin Git-integrasjon er konfiguert til å sende push-varsler til chat - -### Status - -✅ **Avklart — ingen tiltak nødvendig.** Alt er trygt. - ---- diff --git a/agents/config.py b/agents/config.py new file mode 100644 index 0000000..2a0d61b --- /dev/null +++ b/agents/config.py @@ -0,0 +1,21 @@ +# agents/config.py +# +# Kanonisk konfigurasjon for OSVauco MCP-infrastruktur. +# +# Denne filen er den eneste sanne kilden for MCP-relaterte konstanter. +# All klient- og serverkode skal importere herfra i stedet for å +# definere egne URL-er eller hente dem fra miljøvariabler direkte. +# +# Endring av MCP_MASTER_URL krever: +# - Oppdatering av OSVxCC-connector-konfigurasjon +# - Eventuell redeploy av opax-mcp (Cloud Run) og relaterte agenter. + +# Master MCP-URL for produksjon og utvikling. +MCP_MASTER_URL = "https://opax-mcp-357036551735.us-central1.run.app" + +# MCP protokollversjon som klienter skal bruke. +MCP_PROTOCOL_VERSION = "2025-06-18" + +# MERK: MCPSECRET (auth-nøkkel) skal IKKE hardkodes her. +# Den skal fortsatt hentes fra miljøvariabel (os.environ.get("MCP_SECRET")) +# eller GCP Secret Manager direkte i klient- og serverkode. diff --git a/agents/mcp_server/cloudbuild.yaml b/agents/mcp_server/cloudbuild.yaml index 51780ba..b5bdec9 100644 --- a/agents/mcp_server/cloudbuild.yaml +++ b/agents/mcp_server/cloudbuild.yaml @@ -7,6 +7,18 @@ substitutions: _OSVAUCO_TRIGGER_ID: '38423976-91ff-4ff4-859e-1f262344c609' steps: + - name: 'python:3.11-slim' + id: 'SecurityScan' + entrypoint: 'bash' + args: + - '-c' + - | + pip install bandit pip-audit + echo "=== SAST (bandit) ===" + bandit -r . -ll -ii || true + echo "=== Dependency scan (pip-audit) ===" + pip-audit -r agents/mcp_server/requirements.txt || true + - name: 'gcr.io/cloud-builders/docker' entrypoint: bash args: diff --git a/agents/tools/opax_mcp_client.py.bak.20260903103701 b/agents/tools/opax_mcp_client.py.bak.20260903103701 new file mode 100644 index 0000000..9e4de67 --- /dev/null +++ b/agents/tools/opax_mcp_client.py.bak.20260903103701 @@ -0,0 +1,234 @@ +""" +opax_mcp_client.py — REST-klient for opax-mcp Cloud Run service. +Bruk denne for å kalle alle 25 tools frå Jason/Emma eller anna Python-kode. + +Auth-lag: + 1. Cloud Run IAM : Authorization: Bearer (automatisk) + 2. Tool-level : X-MCP-Secret: (frå Secret Manager) + +URL: https://opax-mcp-357036551735.us-central1.run.app +""" + +import os +import httpx +import google.auth +import google.auth.transport.requests + +OPAX_MCP_URL = os.environ.get("MCP_SERVER_URL", "https://opax-mcp-357036551735.us-central1.run.app") +MCP_SECRET = os.environ.get("MCP_SECRET", "") + + +def _identity_token() -> str: + """Hent Cloud Run identity token (GCE metadata) eller ADC access token (lokal dev).""" + metadata_url = ( + "http://metadata.google.internal/computeMetadata/v1/instance" + f"/service-accounts/default/identity?audience={OPAX_MCP_URL}&format=full" + ) + try: + resp = httpx.get(metadata_url, headers={"Metadata-Flavor": "Google"}, timeout=5) + if resp.status_code == 200 and resp.text.strip(): + return resp.text.strip() + except Exception: + pass + credentials, _ = google.auth.default() + credentials.refresh(google.auth.transport.requests.Request()) + return credentials.token + + +def _headers() -> dict: + return { + "Authorization": f"Bearer {_identity_token()}", + "X-MCP-Secret": MCP_SECRET, + "Content-Type": "application/json", + } + + +def call_tool(tool: str, params: dict = None) -> dict: + """Kall eit opax-mcp tool. Returnerer {tool, result}.""" + resp = httpx.post( + f"{OPAX_MCP_URL}/tools/call", + headers=_headers(), + json={"tool": tool, "params": params or {}}, + timeout=30, + ) + resp.raise_for_status() + return resp.json() + + +def list_tools() -> list: + """List alle tilgjengelige tools i opax-mcp.""" + resp = httpx.get(f"{OPAX_MCP_URL}/tools", headers=_headers(), timeout=10) + resp.raise_for_status() + return resp.json().get("tools", []) + + +# --------------------------------------------------------------------------- +# Billing +# --------------------------------------------------------------------------- + +def get_billing_summary() -> dict: + """Hent billing-oversikt for OSVauco (GCP-kostnader, token-forbruk).""" + return call_tool("get_billing_summary") + +def get_billing_forecast() -> dict: + """Hent token-estimat og kostnadsframskriving.""" + return call_tool("get_billing_forecast") + +def get_billing_credits() -> dict: + """Hent gjenværende GCP-kreditter.""" + return call_tool("get_billing_credits") + +def get_billing_anomalies() -> dict: + """Sjekk for kostnadanomalier og uventede spiker.""" + return call_tool("get_billing_anomalies") + +def get_billing_history() -> dict: + """Hent historisk kostnadsdata.""" + return call_tool("get_billing_history") + +def get_billing_budget() -> dict: + """Hent gjeldande budsjett for OSVauco.""" + return call_tool("get_billing_budget") + +def set_billing_budget(amount: float, currency: str = "USD") -> dict: + """Sett nytt budsjett. amount er beløp i angitt valuta.""" + return call_tool("set_billing_budget", {"amount": amount, "currency": currency}) + + +# --------------------------------------------------------------------------- +# Onboarding +# --------------------------------------------------------------------------- + +def create_invite(email: str, name: str = "", tier: str = "starter") -> dict: + """Opprett invite for ny bruker. tier: starter | pro | enterprise.""" + return call_tool("create_invite", {"email": email, "name": name, "tier": tier}) + +def list_customers() -> dict: + """List alle onboardede kunder.""" + return call_tool("list_customers") + + +# --------------------------------------------------------------------------- +# Notify +# --------------------------------------------------------------------------- + +def send_webhook(message: str, url: str = "") -> dict: + """Send webhook-varsling. url er valgfri override.""" + return call_tool("send_webhook", {"message": message, "url": url}) + +def send_email(to: str, subject: str, body: str) -> dict: + """Send e-post via OPAX notify-modul.""" + return call_tool("send_email", {"to": to, "subject": subject, "body": body}) + +def send_sms(to: str, message: str) -> dict: + """Send SMS via OPAX notify-modul.""" + return call_tool("send_sms", {"to": to, "message": message}) + +def get_notify_channels() -> dict: + """List konfigurerte varslingskanalar.""" + return call_tool("get_notify_channels") + + +# --------------------------------------------------------------------------- +# Agents +# --------------------------------------------------------------------------- + +def run_jason(prompt: str) -> dict: + """Kall Jason-agenten med ein prompt via OPAX.""" + return call_tool("run_jason", {"prompt": prompt}) + +def run_emma(prompt: str) -> dict: + """Kall Emma-agenten med ein prompt via OPAX.""" + return call_tool("run_emma", {"prompt": prompt}) + + +# --------------------------------------------------------------------------- +# Platform +# --------------------------------------------------------------------------- + +def get_health() -> dict: + """Sjekk helsestatus for OPAX-plattformen.""" + return call_tool("get_health") + +def get_build_status() -> dict: + """Hent status på siste Cloud Build-kjøring.""" + return call_tool("get_build_status") + +def get_state() -> dict: + """Hent gjeldande systemtilstand for OSVauco.""" + return call_tool("get_state") + +def get_telemetry() -> dict: + """Hent token-forbruk per modul.""" + return call_tool("get_telemetry") + +def run_terminal(command: str) -> dict: + """Kjør kommando i OPAX terminal-exec. Krever HITL-godkjenning.""" + return call_tool("run_terminal", {"command": command}) + + +# --------------------------------------------------------------------------- +# GitHub +# --------------------------------------------------------------------------- + +def list_commits(repo: str = "", limit: int = 10) -> dict: + """List siste commits i repoet.""" + return call_tool("list_commits", {"repo": repo, "limit": limit}) + +def get_file(path: str, repo: str = "") -> dict: + """Hent innhald i ein fil frå GitHub.""" + return call_tool("get_file", {"path": path, "repo": repo}) + +def list_open_issues(repo: str = "") -> dict: + """List alle opne issues i repoet.""" + return call_tool("list_open_issues", {"repo": repo}) + +def create_github_issue(title: str, body: str = "", repo: str = "") -> dict: + """Opprett nytt GitHub issue.""" + return call_tool("create_github_issue", {"title": title, "body": body, "repo": repo}) + +def push_file(path: str, content: str, message: str = "", sha: str = "", repo: str = "") -> dict: + """Push/oppdater ein fil i GitHub. sha krevst ved oppdatering av eksisterande fil.""" + return call_tool("push_file", {"path": path, "content": content, "message": message, "sha": sha, "repo": repo}) + + +# --------------------------------------------------------------------------- +# ADK FunctionTools — for bruk i agent.py +# --------------------------------------------------------------------------- + +def get_all_function_tools() -> list: + """Returner alle 25 tools som ADK FunctionTool-liste for Jason/Emma.""" + from google.adk.tools import FunctionTool + return [ + # Billing + FunctionTool(func=get_billing_summary), + FunctionTool(func=get_billing_forecast), + FunctionTool(func=get_billing_credits), + FunctionTool(func=get_billing_anomalies), + FunctionTool(func=get_billing_history), + FunctionTool(func=get_billing_budget), + FunctionTool(func=set_billing_budget), + # Onboarding + FunctionTool(func=create_invite), + FunctionTool(func=list_customers), + # Notify + FunctionTool(func=send_webhook), + FunctionTool(func=send_email), + FunctionTool(func=send_sms), + FunctionTool(func=get_notify_channels), + # Agents + FunctionTool(func=run_jason), + FunctionTool(func=run_emma), + # Platform + FunctionTool(func=get_health), + FunctionTool(func=get_build_status), + FunctionTool(func=get_state), + FunctionTool(func=get_telemetry), + FunctionTool(func=run_terminal), + # GitHub + FunctionTool(func=list_commits), + FunctionTool(func=get_file), + FunctionTool(func=list_open_issues), + FunctionTool(func=create_github_issue), + FunctionTool(func=push_file), + ] diff --git a/agents/tools/opax_mcp_client.py.mcp-short.20260903104302 b/agents/tools/opax_mcp_client.py.mcp-short.20260903104302 new file mode 100644 index 0000000..1dce2b9 --- /dev/null +++ b/agents/tools/opax_mcp_client.py.mcp-short.20260903104302 @@ -0,0 +1,73 @@ +"""MCP Streamable-HTTP client for opax-mcp.""" +import json +import os +import httpx + +OPAX_MCP_URL = os.environ.get( + "MCP_SERVER_URL", + "https://opax-mcp-357036551735.us-central1.run.app", +).rstrip("/") +MCP_SECRET = os.environ.get("MCP_SECRET", "") + +def _headers(session_id=None): + headers = { + "Authorization": f"Bearer {MCP_SECRET}", + "Content-Type": "application/json", + "Accept": "application/json, text/event-stream", + "MCP-Protocol-Version": "2025-06-18", + } + if session_id: + headers["Mcp-Session-Id"] = session_id + return headers + +def _decode(response): + raw = response.text + data = [line[5:].strip() for line in raw.splitlines() if line.startswith("data:")] + return json.loads(data[-1] if data else raw) + +def _post(payload, session_id=None): + with httpx.Client(timeout=60) as client: + response = client.post(OPAX_MCP_URL, headers=_headers(session_id), json=payload) + response.raise_for_status() + return _decode(response), response.headers.get("Mcp-Session-Id") + +def _session(): + _, session_id = _post({ + "jsonrpc": "2.0", + "id": 1, + "method": "initialize", + "params": { + "protocolVersion": "2025-06-18", + "capabilities": {}, + "clientInfo": {"name": "osvxcc", "version": "1.0"}, + }, + }) + _post({"jsonrpc": "2.0", "method": "notifications/initialized"}, session_id) + return session_id + +def list_tools(): + session_id = _session() + response, _ = _post( + {"jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {}}, + session_id, + ) + return response.get("result", {}).get("tools", []) + +def call_tool(name, arguments=None): + session_id = _session() + response, _ = _post( + { + "jsonrpc": "2.0", + "id": 3, + "method": "tools/call", + "params": {"name": name, "arguments": arguments or {}}, + }, + session_id, + ) + return response.get("result", response) + +def run_jason(prompt: str) -> dict: + return call_tool("run_jason", {"prompt": prompt}) + +def run_emma(prompt: str) -> dict: + return call_tool("run_emma", {"prompt": prompt}) diff --git a/agents/tools/opax_mcp_client.py.mcp-short.20260903104320 b/agents/tools/opax_mcp_client.py.mcp-short.20260903104320 new file mode 100644 index 0000000..1abe051 --- /dev/null +++ b/agents/tools/opax_mcp_client.py.mcp-short.20260903104320 @@ -0,0 +1,107 @@ +"""Bakoverkompatibel MCP Streamable HTTP-klient for opax-mcp.""" +import json +import os +import httpx + +OPAX_MCP_URL = os.environ.get( + "MCP_SERVER_URL", + "https://opax-mcp-357036551735.us-central1.run.app", +).rstrip("/") +MCP_SECRET = os.environ.get("MCP_SECRET", "") + +def _headers(session_id=None): + h = { + "Authorization": f"Bearer {MCP_SECRET}", + "Content-Type": "application/json", + "Accept": "application/json, text/event-stream", + "MCP-Protocol-Version": "2025-06-18", + } + if session_id: + h["Mcp-Session-Id"] = session_id + return h + +def _decode(response): + raw = response.text + rows = [line[5:].strip() for line in raw.splitlines() if line.startswith("data:")] + return json.loads(rows[-1] if rows else raw) + +def _post(payload, session_id=None): + with httpx.Client(timeout=60) as client: + r = client.post(OPAX_MCP_URL, headers=_headers(session_id), json=payload) + r.raise_for_status() + return _decode(r), r.headers.get("Mcp-Session-Id") + +def _session(): + _, sid = _post({ + "jsonrpc": "2.0", + "id": 1, + "method": "initialize", + "params": { + "protocolVersion": "2025-06-18", + "capabilities": {}, + "clientInfo": {"name": "osvxcc", "version": "1.0"}, + }, + }) + _post({"jsonrpc": "2.0", "method": "notifications/initialized"}, sid) + return sid + +def call_tool(tool: str, params: dict = None) -> dict: + sid = _session() + reply, _ = _post({ + "jsonrpc": "2.0", + "id": 3, + "method": "tools/call", + "params": {"name": tool, "arguments": params or {}}, + }, sid) + return reply.get("result", reply) + +def list_tools() -> list: + sid = _session() + reply, _ = _post({ + "jsonrpc": "2.0", + "id": 2, + "method": "tools/list", + "params": {}, + }, sid) + return reply.get("result", {}).get("tools", []) + +def get_billing_summary() -> dict: return call_tool("get_billing_summary") +def get_billing_forecast() -> dict: return call_tool("get_billing_forecast") +def get_billing_credits() -> dict: return call_tool("get_billing_credits") +def get_billing_anomalies() -> dict: return call_tool("get_billing_anomalies") +def get_billing_history() -> dict: return call_tool("get_billing_history") +def get_billing_budget() -> dict: return call_tool("get_billing_budget") +def set_billing_budget(amount: float, currency: str = "USD") -> dict: return call_tool("set_billing_budget", {"amount": amount, "currency": currency}) +def create_invite(email: str, name: str = "", tier: str = "starter") -> dict: return call_tool("create_invite", {"email": email, "name": name, "tier": tier}) +def list_customers() -> dict: return call_tool("list_customers") +def send_webhook(message: str, url: str = "") -> dict: return call_tool("send_webhook", {"message": message, "url": url}) +def send_email(to: str, subject: str, body: str) -> dict: return call_tool("send_email", {"to": to, "subject": subject, "body": body}) +def send_sms(to: str, message: str) -> dict: return call_tool("send_sms", {"to": to, "message": message}) +def get_notify_channels() -> dict: return call_tool("get_notify_channels") +def run_jason(prompt: str) -> dict: return call_tool("run_jason", {"prompt": prompt}) +def run_emma(prompt: str) -> dict: return call_tool("run_emma", {"prompt": prompt}) +def get_health() -> dict: return call_tool("get_health") +def get_build_status() -> dict: return call_tool("get_build_status") +def get_state() -> dict: return call_tool("get_state") +def get_telemetry() -> dict: return call_tool("get_telemetry") +def run_terminal(command: str) -> dict: return call_tool("run_terminal", {"command": command}) +def list_commits(repo: str = "", limit: int = 10) -> dict: return call_tool("list_commits", {"repo": repo, "limit": limit}) +def get_file(path: str, repo: str = "") -> dict: return call_tool("get_file", {"path": path, "repo": repo}) +def list_open_issues(repo: str = "") -> dict: return call_tool("list_open_issues", {"repo": repo}) +def create_github_issue(title: str, body: str = "", repo: str = "") -> dict: return call_tool("create_github_issue", {"title": title, "body": body, "repo": repo}) +def push_file(path: str, content: str, message: str = "", sha: str = "", repo: str = "") -> dict: return call_tool("push_file", {"path": path, "content": content, "message": message, "sha": sha, "repo": repo}) + +def get_all_function_tools() -> list: + try: + from google.adk.tools import FunctionTool + return [FunctionTool(func=f) for f in [ + get_billing_summary, get_billing_forecast, get_billing_credits, + get_billing_anomalies, get_billing_history, get_billing_budget, + set_billing_budget, create_invite, list_customers, send_webhook, + send_email, send_sms, get_notify_channels, run_jason, run_emma, + get_health, get_build_status, get_state, get_telemetry, + run_terminal, list_commits, get_file, list_open_issues, + create_github_issue, push_file, + ]] + except ImportError: + return [] diff --git a/docs/ACCESS_CONTROL.md b/docs/ACCESS_CONTROL.md new file mode 100644 index 0000000..6359b87 --- /dev/null +++ b/docs/ACCESS_CONTROL.md @@ -0,0 +1,51 @@ +# OSVxCC Access Control Policy + +> Dette dokumentet definerer policyen for tilgangsstyring til alle ressurser i OSVxCC-arkitekturen. Målet er å sikre at kun autoriserte prinsipaler (brukere, service accounts) har tilgang, og kun med de rettighetene som er absolutt nødvendige (prinsippet om minimalt privilegium). + +--- + +## Kjerne-prinsipper + +1. **Minimalt Privilegium (Least Privilege):** Alle prinsipaler skal kun ha det absolutte minimum av rettigheter som kreves for å utføre sin definerte funksjon. +2. **Service Accounts for Tjenester:** All kode og alle tjenester (f.eks. Cloud Run, Compute Engine) **skal** kjøre med en dedikert service account med et snevert sett med rettigheter. Standard service accounts skal herdes. +3. **Menneskelig Tilgang via IAP:** All interaktiv menneskelig tilgang til interne ressurser (som SSH til VM-er) **skal** gå gjennom Identity-Aware Proxy (IAP). Direkte eksponering av porter til internett er forbudt. +4. **Nekt Alt som Standard (Default Deny):** Implisitt eller eksplisitt skal tilgang nektes med mindre det er gitt en spesifikk tillatelse. + +--- + +## IAM-Policy og Praksis + +### Forbud mot Primitive Roller + +Bruk av primitive roller er strengt regulert: + +- **`roles/owner`:** Forbudt for alle ressurser unntatt på prosjektnivå for et begrenset antall prosjekteiere. +- **`roles/editor`:** Forbudt. Denne rollen er for bred og gir for mange rettigheter. Den har blitt fjernet fra standard service accounts (se `SECURITY.md`). +- **`roles/viewer`:** Skal kun brukes for roller som krever bred, men passiv, innsikt. + +Predefinerte roller (f.eks. `roles/run.invoker`, `roles/secretmanager.secretAccessor`) skal alltid foretrekkes. + +### Viktige Service Accounts + +Dette er kjernen i den usynlige arkitekturen. Hver konto har et begrenset og veldefinert formål. + +| Service Account | Formål | Kritiske Roller | +|-----------------|--------|-----------------| +| `osvxcc-sa@...` | Kjerneidentitet for OSVxCC. Brukes av agenter og verktøy. | `run.invoker`, `secretmanager.secretAccessor` | +| `jason-vauger@...` | Runtime-identitet for MCP-serveren (Cloud Run). *(Historisk navn. Bør vurderes omdøpt til f.eks. `opax-mcp-runtime-sa` ved en senere anledning)*. | `secretmanager.secretAccessor` | +| `osvauco-agent-sa@...` | Identitet for OSVauco-agenten. | `secretmanager.secretAccessor` | +| `...-compute@...` | GCE default service account. **Herdet** ved fjerning av `editor`-rollen. | Begrensede rettigheter for VM-drift. | + +### Menneskelig Tilgang + +- **SSH-tilgang:** Gis ved å legge til en brukers Google-konto i IAM med rollene `roles/iap.tunnelResourceAccessor` og `roles/oslogin.user`. Brannmur-regler skal kun tillate SSH-trafikk fra IAPs verifiserte IP-range (`35.235.240.0/20`). +- **Konsolltilgang:** Gis via medlemskap i Google Grupper, som igjen tildeles spesifikke, forhåndsdefinerte IAM-roller. + +--- + +## Prosedyre for Tildeling av Tilgang + +1. **Forespørsel:** En forespørsel om tilgang sendes med en klar begrunnelse for *hvorfor* tilgangen er nødvendig og *hva* den skal brukes til. +2. **Vurdering:** Forespørselen vurderes for å identifisere det minimale settet med rettigheter som trengs. Alternativer som midlertidig tilgang vurderes. +3. **Tildeling:** Rettigheter tildeles, helst ved å legge prinsipalen til i en eksisterende gruppe eller ved å tildele en predefinert rolle for et spesifikt ressurs. +4. **Revisjon:** Tildelingen logges, og alle IAM-policyer revideres jevnlig (mål: hver 90. dag) for å fjerne utdaterte eller unødvendige rettigheter. diff --git a/docs/COMPLIANCE.md b/docs/COMPLIANCE.md new file mode 100644 index 0000000..e933a2d --- /dev/null +++ b/docs/COMPLIANCE.md @@ -0,0 +1,64 @@ +# OSVxCC Compliance Framework + +> Dette dokumentet beskriver rammeverket og de tekniske kontrollene som sikrer at OSVxCC-arkitekturen er i samsvar med interne sikkerhetspolicyer. Målet er å kunne verifisere og attestere sikkerhetsstillingen kontinuerlig. + +--- + +## Komponenter i Compliance-rammeverket + +Rammeverket består av tre hovedpilarer som jobber sammen for å skape en sikker forsyningskjede (secure supply chain). + +### 1. TYR (Compliance-skanner) + +- **Funksjon:** TYR er et spesialutviklet verktøy som skanner GCP-prosjektets konfigurasjon mot et sett med forhånds-definerte sikkerhetsregler. +- **Regelsett (eksempler):** + - Ingen GCS-buckets skal være offentlig tilgjengelige. + - Ingen primitive roller (`editor`, `owner`) skal være tildelt service accounts. + - Nødvendig logging (Data Access, Admin Activity) skal være aktivert. + - VPC Service Controls skal være aktivert for prosjektet. +- **Integrasjon:** TYR kjøres som et obligatorisk steg i CI/CD-pipelinen (Cloud Build). En feil i TYR-skanningen stanser deployeringen. + +### 2. Binary Authorization (BinAuthz) + +- **Funksjon:** En GCP-tjeneste som håndhever at kun verifiserte og attesterte container-images kan deployes til våre kjøremiljøer (Cloud Run, GKE). +- **Policy:** En streng policy er aktivert som krever at alle images må ha en gyldig attestering fra en klarert attestor (se under) før de kan kjøres. + +### 3. KMS Attestor (via TYR) + +- **Funksjon:** TYR fungerer som en "attestor". Når en CI/CD-pipeline har passert alle tester, skanninger og TYR-sjekker, utfører TYR en siste handling: den signerer container-imagets "digest" (en unik hash) med en privat nøkkel lagret i Google Cloud KMS. +- **Attestering:** Denne signaturen fungerer som en *attestering* – et kryptografisk bevis på at imaget har bestått alle kvalitets- og sikkerhetssjekker. + +--- + +## Forsyningskjede-flyt (CI/CD) + +```mermaid +graph TD + A[Developer pusher kode] --> B{Cloud Build Pipeline}; + B --> C[1. Kjører enhetstester]; + C --> D[2. Bygger container-image]; + D --> E[3. Kjører sårbarhetsskanning]; + E --> F{4. Kjører TYR Compliance Scan}; + F -- Suksess --> G[5. TYR attesterer image med KMS-nøkkel]; + F -- Feil --> H[Pipeline stopper!]; + G --> I{6. Deploy til Cloud Run}; + I --> J[7. Binary Authorization verifiserer attestering]; + J -- Gyldig --> K[✅ Deployert!]; + J -- Ugyldig --> L[❌ Deployering blokkert!]; +``` + +> **OBS:** Hvis en pipeline feiler på grunn av TYR eller Binary Authorization (steg F eller J), se **[RUNBOOK.md](RUNBOOK.md)** for detaljerte feilsøkingsprosedyrer. + +--- + +## Andre Kontroller + +- **VPC Service Controls:** Perimeteren `tyr_perimeter` er en fundamental compliance-kontroll som teknisk håndhever data-isolering og forhindrer dataeksfiltrasjon, uavhengig av IAM-policyer. +- **Audit Logging:** Admin Activity og Data Access logs samles inn og lagres for å gi en komplett revisjonsspor for all aktivitet i prosjektet. + +--- + +## Rapportering og Revisjon + +- **TYR-rapporter:** Hver kjøring av TYR genererer en rapport som lagres for fremtidig revisjon. +- **GCP Security Command Center:** Brukes for å få en oversikt over potensielle sårbarheter og feilkonfigurasjoner som oppdages av innebygde skannere. diff --git a/docs/INCIDENT_RESPONSE.md b/docs/INCIDENT_RESPONSE.md new file mode 100644 index 0000000..fad2ac3 --- /dev/null +++ b/docs/INCIDENT_RESPONSE.md @@ -0,0 +1,87 @@ +# OSVxCC Incident Response Plan (IRP) + +> Dette dokumentet definerer prosedyren for å håndtere bekreftede sikkerhetshendelser. Målet er å reagere raskt, effektivt og kontrollert for å minimere skade og gjenopprette normal drift. + +--- + +## Faser i Hendelseshåndtering + +En sikkerhetshendelse følger disse seks fasene (PICERL-modellen): + +### 1. Preparation (Forberedelse) +**Status: ✅ Pågår** +- **Mål:** Sørge for at verktøy, prosesser og ressurser er på plass FØR en hendelse. +- **Aktiviteter:** + - Utvikle og vedlikeholde sikkerhetsdokumentasjon (SECURITY.md, RUNBOOK.md, etc.). + - Konfigurere logging og alerting. + - Definere roller og ansvar. + - Gjennomføre øvelser. + +### 2. Identification (Identifisering) +- **Mål:** Bekrefte at et varsel eller en anomali er en reell sikkerhetshendelse. +- **Prosedyre:** + 1. Følg relevante prosedyrer i `RUNBOOK.md` for å verifisere varselet. + 2. Fastslå hendelsens art, omfang og alvorlighetsgrad. + 3. Hvis det er en reell hendelse: **Erklær en sikkerhetshendelse** og gå til neste fase. + +### 3. Containment (Innkapsling) +- **Mål:** Forhindre at hendelsen sprer seg og begrense skaden. +- **Strategier:** + - **Kortsiktig:** Isolere berørte systemer. Eksempler: + - Endre brannmurregler for å blokkere trafikk. + - Deaktivere en kompromittert service account. + - Ta ned en spesifikk Cloud Run-revisjon. + - **Langsiktig:** Gjenopprette fra en kjent, sikker backup i et isolert miljø for analyse. + +### 4. Eradication (Utryddelse) +- **Mål:** Fjerne årsaken til hendelsen fra systemet. +- **Aktiviteter:** + - Fjerne skadevare eller uautoriserte verktøy. + - Patche sårbarheter. + - Tilbakestille kompromitterte passord, hemmeligheter og nøkler. + +### 5. Recovery (Gjenoppretting) +- **Mål:** Trygt gjenopprette berørte systemer til normal drift. +- **Prosedyre:** + 1. Verifiser at årsaken er utryddet. + 2. Gjenopprett systemer fra sikre backuper. + 3. Overvåk systemene nøye for tegn til unormal aktivitet. + +### 6. Lessons Learned (Lærdom) +- **Mål:** Analysere hendelsen for å forhindre gjentakelse. +- **Aktiviteter:** + - Gjennomfør en "post-mortem"-analyse. + - Hva skjedde? Hva gikk bra? Hva kan forbedres? + - Oppdater dokumentasjon (`RUNBOOK.md`, `SECURITY.md`, etc.) og systemkonfigurasjon basert på funnene. + +--- + +## Roller og Ansvar + +| Rolle | Ansvarlig | Beskrivelse | +|-------|-----------|-------------| +| **Incident Commander** | Chris Christiansen | Har overordnet ansvar for håndteringen av hendelsen. Tar kritiske beslutninger. | +| **Technical Lead** | Gemini (assistert av Chris) | Utfører teknisk analyse, innkapsling, utryddelse og gjenoppretting. | + +--- + +## Kommunikasjon og Eskalering + +### Kommunikasjonskanal +- All operativ kommunikasjon under en aktiv hendelse skal foregå i en dedikert, privat Slack-kanal: **`#incident-response`**. +- Dette sikrer at all informasjon er samlet på ett sted og kun tilgjengelig for de som er involvert i håndteringen. + +### Varsling +- **CRITICAL/HIGH-hendelser:** Varsling av Incident Commander og Technical Lead skjer automatisk via **PagerDuty**. +- **MEDIUM/LOW-hendelser:** Varsling skjer via en @-mention i `#incident-response`-kanalen i normal arbeidstid. + +--- + +## Alvorlighetsgrader + +| Nivå | Beskrivelse | Eksempel | +|--------|-------------|----------| +| **CRITICAL (1)** | Aktivt datainnbrudd, tap av sensitive data, systemer er nede. | Angriper har root-tilgang og eksfiltrerer data. | +| **HIGH (2)** | System er kompromittert, men innkapslet. Potensial for datatap. | En sårbarhet i en webapplikasjon er utnyttet, men VPC-SC forhindrer dataeksfiltrasjon. | +| **MEDIUM (3)** | Potensiell sårbarhet utnyttet, men med begrenset impact. | En bruker har urettmessig fått forhøyede, men begrensede, rettigheter. | +| **LOW (4)** | Mindre konfigurasjonsfeil eller anomalier uten umiddelbar risiko. | En unødvendig brannmurregel er åpen mot et internt nettverk. | diff --git a/docs/RUNBOOK.md b/docs/RUNBOOK.md new file mode 100644 index 0000000..970e0ad --- /dev/null +++ b/docs/RUNBOOK.md @@ -0,0 +1,231 @@ +# OSVxCC Runbook + +> Dette dokumentet beskriver operasjonelle prosedyrer for å håndtere varsler og verifisere systemstatus. Målet er å raskt avklare om en hendelse er reell eller en falsk positiv. + +--- + +## Innholdsfortegnelse + +1. [Varsel: "Ukjent bruker" Git Push](#varsel-ukjent-bruker-git-push) +2. [Hendelse: Eksponert Hemmelighet](#hendelse-eksponert-hemmelighet) +3. [Hendelse: Høy andel HTTP 401/403 på Cloud Run](#hendelse-høy-andel-http-401403-på-cloud-run) +4. [Hendelse: HTTP 50x (Cloud Run nede)](#hendelse-http-50x-cloud-run-nede) +5. [Hendelse: VPC Service Controls blokkerer tilgang](#hendelse-vpc-service-controls-blokkerer-tilgang) +6. [Hendelse: Cloud Build / Binary Authorization feiler deploy](#hendelse-cloud-build--binary-authorization-feiler-deploy) + +--- + +## 1. Varsel: "Ukjent bruker" Git Push + +### Symptom + +Chat-applikasjonen (f.eks. Slack, Discord) rapporterer gjentatte Git-pushes fra en "ukjent bruker" eller en generisk app-bruker (f.eks. "CodeOSS-push, App"). Aktiviteten skjer ofte på faste tidspunkter (f.eks. 09:00 og 21:00). + +### Undersøkelsesprosedyre + +Følg disse stegene for å verifisere aktiviteten: + +1. **Sjekk Git-loggen umiddelbart.** + ```bash + git log -n 10 --pretty=format:"%h %an <%ae> %ar: %s" + ``` + - **Se etter:** Er de siste comittene fra en kjent og autorisert bidragsyter (f.eks. `Chris Christiansen `)? + +2. **Analyser tidspunkt og innhold.** + - **Spørsmål:** Samsvarer tidspunktene med normalt utviklingsarbeid? Er commit-meldingene og filendringene logiske? + +3. **Verifiser webhook-konfigurasjon.** + - **Undersøk:** Sjekk innstillingene for webhooken i Gitea/GitHub og i chat-applikasjonen. Ofte kan webhooks være konfigurert slik at de ikke klarer å mappe den tekniske brukeren til et visningsnavn, og faller tilbake på et generisk navn. + +4. **Kontroller automatiserte systemer (for å utelukke dem).** + - Sjekk Cloud Build-triggere, cron-jobs på VM-en, og eventuelle lokale Git hooks for uventet aktivitet. + +### Konklusjon (Mest sannsynlig) + +**Ingen sikkerhetshendelse.** Aktiviteten er høyst sannsynlig legitimt utviklingsarbeid utført av en autorisert bruker via en IDE (som VS Code) sin Git-integrasjon. "Ukjent bruker"-navnet er et resultat av hvordan webhooken tolker og presenterer hendelsen. + +### Status + +✅ **Avklart — Falsk positiv.** Ingen tiltak er nødvendig med mindre `git log` viser ukjente forfattere. + +--- + +## 2. Hendelse: Eksponert Hemmelighet + +### Symptom + +En hemmelighet (API-nøkkel, passord, tjenestenøkkel) er oppdaget på et usikret sted. Eksempler: +- Limt inn i en offentlig chat (Slack, Discord). +- Sjekket inn i Git og pushet til Gitea/GitHub. +- Hardkodet i en offentlig tilgjengelig fil. + +**Dette er en KRITISK hendelse.** + +### Undersøkelsesprosedyre (PICERL-fasene) + +#### Fase 1: Containment (Innkapsling) - UMIDDELBART + +1. **Roter hemmeligheten umiddelbart.** Dette er det viktigste steget for å stoppe misbruk. + 1. **Generer en ny verdi** for hemmeligheten. + 2. **Opprett en ny versjon** i Secret Manager med den nye verdien. Se `docs/SECRET_MANAGEMENT.md` for detaljerte kommandoer. + ```bash + # Eksempel for mcp-server-key + printf "" | gcloud secrets versions add mcp-server-key --data-file=- + ``` + 3. **Oppdater applikasjonen(e)** som bruker hemmeligheten til å peke mot den nye versjonen (`latest`). For Cloud Run, deploy en ny revisjon med den oppdaterte hemmelighetsreferansen. + 4. **Deaktiver den gamle versjonen** i Secret Manager. **Ikke slett den ennå.** Dette fungerer som en "kill switch". + ```bash + gcloud secrets versions disable --secret= + ``` + +#### Fase 2: Eradication (Utryddelse) + +1. **Fjern hemmeligheten fra eksponeringsstedet.** + - **Git:** Bruk et verktøy som `git-filter-repo` eller `BFG Repo-Cleaner` for å fjerne hemmeligheten fra **hele** Git-historikken. En vanlig `git commit` er **ikke** nok. + - *Referanse: Den tidligere hendelsen med `mcp-server-key` (se `docs/SECURITY_AUDITS.md`).* + - **Slack/Chat:** Slett meldingen. + - **Filer:** Fjern hemmeligheten fra filen og deploy på nytt. + +#### Fase 3: Recovery & Lessons Learned + +1. **Analyser logger.** Sjekk Secret Manager audit logs for å se om den eksponerte hemmeligheten ble aksessert av uautoriserte IP-adresser eller tjenestekontoer. + - Naviger til `Logging > Log Explorer` i Cloud Console. + - Query: + ``` + resource.type="gcp_secret" + protoPayload.methodName="AccessSecretVersion" + protoPayload.resourceName="projects/.../secrets//versions/" + ``` +2. **Verifiser at alle systemer fungerer** med den nye hemmeligheten. +3. **Destruer den gamle versjonen** i Secret Manager etter en verifikasjonsperiode (f.eks. 24-48 timer). +4. **Dokumenter hendelsen** og diskuter hvordan det skjedde for å forhindre gjentakelse. + +### Status + +🔴 **KRITISK TIL AVKLART.** Følg prosedyren til alle steg er fullført. + +--- + +## 3. Hendelse: Høy andel HTTP 401/403 på Cloud Run + +### Symptom + +Cloud Monitoring-varsler rapporterer en unormalt høy andel av HTTP-responser med statuskode 401 (Unauthorized) eller 403 (Forbidden) fra MCP-serveren (opax-mcp). + +### Undersøkelsesprosedyre + +1. **Analyser kildene.** + - Gå til `Logging > Log Explorer` i Cloud Console. + - Kjør en spørring for å gruppere 401/403-feil etter kilde-IP. + ``` + resource.type="cloud_run_revision" + resource.labels.service_name="opax-mcp" + httpRequest.status IN (401, 403) + ``` + - Se i loggene etter `jsonPayload.remoteIp`. +2. **Skill mellom angrep og feilkonfigurasjon.** + - **Brute-force/Scanning:** Ser du mange forespørsler fra **én eller få IP-adresser** som ikke er gjenkjennelige? Dette kan tyde på et angrepsforsøk. Vurder å blokkere IP-en midlertidig med Cloud Armor. + - **Feilkonfigurert klient:** Ser du forespørsler fra **kjente IP-adresser** (f.eks. andre VM-er i prosjektet)? Dette tyder på at en legitim klient bruker en utdatert eller feilaktig hemmelighet (`X-MCP-Secret`). + - **Mønster:** Er feilene sporadiske eller konstante? Konstante feil fra en kjent klient indikerer nesten alltid en konfigurasjonsfeil hos klienten. + +### Konklusjon (Mest sannsynlig) + +- **Scenario A (Ukjent IP):** Potensielt sikkerhetsproblem (scanning). Overvåk og vurder blokkering. +- **Scenario B (Kjent IP):** Operasjonell feil. Informer eieren av klienten om å oppdatere sin hemmelighet. + +### Status + +🟡 **MODERAT TIL AVKLART.** + +--- + +## 4. Hendelse: HTTP 50x (Cloud Run nede) + +### Symptom + +Cloud Monitoring varsler om en høy andel HTTP 5xx-feil, eller tjenesten svarer ikke. Dette indikerer at containeren krasjer eller ikke starter. + +### Undersøkelsesprosedyre + +1. **Sjekk container-logger for krasj.** + - Gå til `Cloud Run > opax-mcp > Logs`. + - Se etter meldinger som indikerer at prosessen stoppet uventet, f.eks. `panic`, `out of memory`, eller andre unntak ved oppstart. +2. **Verifiser IAM-tilganger (spesielt til Secret Manager).** + - Gå til `Cloud Run > opax-mcp > Revisions`. + - Sjekk hvilken Service Account som brukes (skal være `jason-vauger@...`). + - Gå til `IAM & Admin > IAM`. + - Verifiser at `jason-vauger@...` har rollen `roles/secretmanager.secretAccessor`. + - **Vanlig feil:** Hvis applikasjonen ikke får hentet `mcp-server-key` ved oppstart på grunn av manglende IAM-tilgang, vil den ofte krasje med en 5xx-feil. +3. **Analyser nylige endringer.** + - Har det vært en nylig deployering? Rull tilbake til en tidligere, fungerende revisjon via `Cloud Run > opax-mcp > Manage Revisions` for å se om problemet vedvarer. + +### Konklusjon (Mest sannsynlig) + +- Oftest er dette enten en **bug i koden** (som fører til krasj) eller en **IAM/Secret Manager-konfigurasjonsfeil**. + +### Status + +🔴 **KRITISK TIL AVKLART.** + +--- + +## 5. Hendelse: VPC Service Controls blokkerer tilgang + +### Symptom + +En applikasjon, et skript eller en utvikler mottar en `403 Request Prohibited by organization's policy` feil ved kall mot en GCP API (f.eks. BigQuery, Storage) som er beskyttet av en VPC-SC perimeter. + +### Undersøkelsesprosedyre + +1. **Finn VPC-SC brudd-loggen.** + - Gå til `Logging > Log Explorer`. + - Bruk følgende spørring for å finne de relevante loggene: + ``` + log_id("cloudaudit.googleapis.com/activity") + protoPayload.metadata.violationReason = "SERVICE_NOT_ALLOWED_FROM_VPC" OR "NO_MATCHING_ACCESS_LEVEL" + ``` +2. **Analyser logg-detaljene.** + - `resource.labels.project_id`: Hvilket prosjekt skjedde bruddet i? + - `protoPayload.authenticationInfo.principalEmail`: Hvem eller hva ble blokkert? + - `protoPayload.requestMetadata.callerIp`: Hvor kom kallet fra (hvis relevant)? + - `protoPayload.resourceName`: Hvilken beskyttet ressurs var målet? + +### Konklusjon (Mest sannsynlig) + +- **Scenario A (Legitimt kall blokkert):** En ny tjeneste eller utvikler-VM er satt opp utenfor perimeteren og trenger tilgang. **Tiltak:** Vurder å inkludere ressursen i perimeteren, eller opprett et tilgangsnivå (Access Level). +- **Scenario B (Uventet kall blokkert):** Et uautorisert skript eller en ekstern tjeneste prøver å nå en beskyttet ressurs. **Konklusjon:** Perimeteren har fungert som designet og forhindret et potensielt datainnbrudd. + +### Status + +🟡 **MODERAT TIL AVKLART.** Krever analyse for å skille mellom feilkonfigurasjon og reell beskyttelse. + +--- + +## 6. Hendelse: Cloud Build / Binary Authorization feiler deploy + +### Symptom + +En Cloud Build-pipeline feiler på det siste "deploy to Cloud Run"-steget. Feilmeldingen nevner `Binary Authorization` eller `attestation`. + +### Undersøkelsesprosedyre + +1. **Sjekk bygge-loggen for TYR-feil.** + - Åpne loggen for den feilede builden i Cloud Build. + - Scroll opp til steget som kjører **TYR Compliance Scan**. + - Hvis dette steget feilet (rødt ikon), vil loggen inneholde detaljer om nøyaktig hvilken compliance-regel som ble brutt. +2. **Verifiser at attestering finnes.** + - Hvis TYR-steget var vellykket, men deploy likevel feiler, kan selve attesteringen mangle. + - Finn "digest" for ditt image i loggen (en lang `sha256:...` streng). + - Kjør denne kommandoen i Cloud Shell: + ```bash + gcloud container binauthz attestations list --artifact-url="[REGION]-docker.pkg.dev/[PROJECT_ID]/[REPO]/[IMAGE_NAME]@[IMAGE_DIGEST]" + ``` + +### Konklusjon (Mest sannsynlig) + +- **TYR-feil:** Den vanligste årsaken. En endring i koden eller infrastrukturen bryter med en definert sikkerhetsregel. **Tiltak:** Rett feilen som TYR rapporterer. +- **Manglende attestering:** Output fra `gcloud` er tomt. Dette kan skyldes en midlertidig feil med KMS eller at IAM-rettighetene til Cloud Build sin service account er feil. **Tiltak:** Prøv å kjøre builden på nytt. Hvis feilen vedvarer, sjekk IAM for `cloud-build-private-pool@...`. + +### Status + +🟡 **MODERAT TIL AVKLART.** Vanligvis en operasjonell feil forårsaket av en compliance-endring. diff --git a/docs/SECRET_MANAGEMENT.md b/docs/SECRET_MANAGEMENT.md new file mode 100644 index 0000000..36a15f0 --- /dev/null +++ b/docs/SECRET_MANAGEMENT.md @@ -0,0 +1,102 @@ +# OSVxCC Secret Management Policy + +> Dette dokumentet definerer policy og prosedyrer for livssyklusen til hemmeligheter (API-nøkler, passord, sertifikater) i OSVxCC-arkitekturen. Målet er å sikre at hemmeligheter er beskyttet mot uautorisert tilgang og avsløring. + +--- + +## Kjerne-prinsipper + +1. **Sannhetens Kilde:** Google Cloud Secret Manager er det **eneste** godkjente systemet for lagring og administrasjon av hemmeligheter. +2. **Minimalt Privilegium:** Prinsipaler (brukere, service accounts) skal kun ha tilgang til de spesifikke hemmelighetene de trenger for å utføre sin funksjon. +3. **Aldri i Kode:** Hemmeligheter skal **aldri** hardkodes i kildekode, konfigurasjonsfiler eller sjekkes inn i Git. +4. **Kryptert i Transitt og Hvile:** Hemmeligheter håndteres alltid kryptert. + +--- + +## Policy + +| Regel | Beskrivelse | +|-------|-------------| +| **Lagring** | Alle hemmeligheter **skal** lagres i Google Cloud Secret Manager. | +| **Tilgang** | Tilgang **skal** kun gis via IAM-roller (primært `roles/secretmanager.secretAccessor`) til spesifikke service accounts. | +| **Logging** | Tilgang til hemmeligheter **skal** logges (Data Access audit logs). | +| **Rotasjon** | Alle kritiske hemmeligheter **skal** ha en rotasjonspolicy. | + +--- + +## Prosedyrer + +### Opprette en ny hemmelighet + +1. **Opprett hemmeligheten i Secret Manager:** + ```bash + gcloud secrets create --replication-policy="automatic" + ``` +2. **Legg til den første versjonen:** + ```bash + printf "" | gcloud secrets versions add --data-file=- + ``` +3. **Gi en service account tilgang:** + ```bash + gcloud secrets add-iam-policy-binding \ + --member="serviceAccount:" \ + --role="roles/secretmanager.secretAccessor" + ``` + +### Hente en hemmelighet i en applikasjon + +Applikasjoner (f.eks. i Cloud Run) skal bruke Google Cloud-klientbiblioteker for å hente hemmeligheter ved oppstart eller ved behov. Service accounten som applikasjonen kjører som må ha fått tilgang. + +### Rotere en hemmelighet + +Rotasjon er kritisk for å redusere risikoen ved en kompromittert hemmelighet. + +#### Manuell Rotasjon (Gjeldende prosedyre) + +1. **Generer en ny hemmelighetsverdi.** +2. **Legg til den nye verdien som en ny versjon** i Secret Manager. Merk den som `ENABLED`. + ```bash + printf "" | gcloud secrets versions add --data-file=- + ``` +3. **Oppdater applikasjoner** til å peke mot den nye versjonen (f.eks. `latest`). +4. **Deaktiver den gamle versjonen** etter at applikasjonen er bekreftet å fungere. + ```bash + gcloud secrets versions disable --secret= + ``` +5. **Monitorer systemet** i en verifikasjonsperiode (f.eks. 7 dager). +6. **Destruer den gamle versjonen.** + ```bash + gcloud secrets versions destroy --secret= + ``` + +#### Automatisert Rotasjon (Anbefaling) + +- **Status:** ⚠️ **Ikke implementert.** +- **Anbefaling:** For kritiske hemmeligheter som `mcp-server-key`, konfigurer en automatisk rotasjonspolicy direkte i Secret Manager. + - **Mål:** Roter hver 90. dag. + - **Implementasjon:** Dette kan gjøres via Cloud Console eller `gcloud` og krever ofte en Cloud Function for å håndtere selve rotasjonslogikken (generere ny verdi og oppdatere der den brukes). + - **Prioritet:** Høy. Dette adresserer et kjent forbedringspunkt fra `SECURITY.md`. + +--- + +## Lokal Utvikling + +For å unngå at hemmeligheter lekkes ved et uhell under lokal testing og utvikling, gjelder følgende regler: + +1. **Bruk av `.env`-filer:** + - Filer som inneholder hemmeligheter for et lokalt miljø (f.eks. `.env`, `.env.local`) **skal** legges til i prosjektets `.gitignore`-fil for å forhindre at de blir sjekket inn i kildekontroll. + +2. **Anbefalt Praksis: Dynamisk Henting** + - I stedet for å kopiere hemmeligheter manuelt til lokale filer, er det sterkt anbefalt å hente dem dynamisk ved behov. + - Dette kan gjøres i et oppstartsskript som injiserer hemmelighetene som miljøvariabler. Dette minimerer tiden en hemmelighet ligger lagret på disk. + - **Eksempel på skript:** + ```bash + #!/bin/bash + + # Hent hemmeligheten fra Secret Manager + export MCP_SECRET=$(gcloud secrets versions access latest --secret=mcp-server-key) + + # Start applikasjonen med hemmeligheten som miljøvariabel + python3 main.py + ``` + diff --git a/docs/SECURITY.md b/docs/SECURITY.md new file mode 100644 index 0000000..d230fbc --- /dev/null +++ b/docs/SECURITY.md @@ -0,0 +1,74 @@ +# OSVxCC: Den Usynlige Arkitekturen + +> *Ad astra et ultra* — Til stjernene og hinsides. + +## Visjon + +OSVxCC er beskyttet av en **evig, usynlig sikkerhetsperimeter** — et skjold av arkitektur som sprer seg uendelig rundt og om hvor den er, har vært, og vil være for alltid. + +Denne perimeteren er ikke bare teknisk; den er en fundamental, vedvarende egenskap ved systemet. Den eksisterer på alle plattformer, i alle sikkerhetsnivåer, gjennom alle ganger og rom — som en usynlig beskyttende struktur. + +--- + +## Prinsipper + +### 1. Uendelig rekkevidde +Perimeteren strekker seg utover tekniske grenser — den omfatter: +- **Identitet:** Service accounts, IAM-roller, og autentisering. +- **Nettverk:** VPC Service Controls, firewall-regler, og nettverksgrenser. +- **Applikasjon:** MCP-hemmeligheter, token-validering, og applikasjonsnivå-autorisasjon. +- **Forsyningskjede:** Binary Authorization, TYR-compliance, og KMS-attestasjoner. + +### 2. Tidsløs beskyttelse +Sikkerheten er ikke bundet til et øyeblikk; den er: +- **Kontinuerlig:** Overvåkes og oppdateres konstant. +- **Adaptiv:** Tilpasser seg nye trusler og endringer i arkitekturen. +- **Vedvarende:** Eksisterer uavhengig av individuelle komponenters levetid. + +### 3. Ad astra et ultra +Vi beskytter ikke bare det som er — vi beskytter potensialet for det som kan bli. Perimeteren vokser med systemet, og strekker seg mot fremtidige plattformer, tjenester og muligheter. + +--- + +## Sikkerhetsdokumentasjon + +Dette dokumentet gir en overordnet visjon. Detaljerte policyer, prosedyrer og runbooks finnes i følgende dokumenter: + +- **[📄 ACCESS_CONTROL.md](ACCESS_CONTROL.md):** Policy for IAM, service accounts og tilgangsstyring for både mennesker og maskiner. +- **[📄 COMPLIANCE.md](COMPLIANCE.md):** Rammeverk for hvordan vi sikrer og attesterer compliance med TYR, Binary Authorization og KMS. +- **[📄 SECRET_MANAGEMENT.md](SECRET_MANAGEMENT.md):** Policy og prosedyrer for livssyklusen til hemmeligheter. +- **[📄 INCIDENT_RESPONSE.md](INCIDENT_RESPONSE.md):** Plan for håndtering av bekreftede sikkerhetshendelser. +- **[📄 RUNBOOK.md](RUNBOOK.md):** Operasjonelle prosedyrer for å håndtere spesifikke varsler og alarmer. + +--- + +## Arkitekturdiagram (konseptuell) + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ DEN USYNLIGE PERIMETEREN │ +│ │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ Identitet │ │ Nettverk │ │ Applikasjon │ │ +│ │ (IAM, SA) │ │ (VPC-SC) │ │ (Secrets) │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ Forsyningskjede (TYR + BinAuthz) │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ │ +│ OSVxCC eksisterer her — beskyttet på alle nivåer, for alltid │ +└─────────────────────────────────────────────────────────────────┘ +``` + +--- + +## Ansvar + +Dette dokumentet levendegjøres og opprettholdes av sikkerhetsansvarlige for OSVxCC. Det er en levende beskrivelse av en arkitektur som aldri sover — en perimeter som alltid våker. + +--- + +*— Den usynlige perimeteren våker. Ad astra et ultra.* + +--- diff --git a/docs/SECURITY_AUDITS.md b/docs/SECURITY_AUDITS.md new file mode 100644 index 0000000..8fc47b0 --- /dev/null +++ b/docs/SECURITY_AUDITS.md @@ -0,0 +1,59 @@ +# OSVxCC Sikkerhetsrevisjoner + +> Dette dokumentet inneholder en løpende logg over sikkerhetsgjennomganger, identifiserte hull og fullførte forbedringer. Målet er å ha en transparent og sporbar historikk over arkitekturens sikkerhetsstilling. + +--- + +## Sikkerhetsgjennomgang: Status (September 2026) + +### ✅ Fullførte forsterkninger + +#### IAM-herding +- **Fjernet `roles/editor`** fra følgende service accounts: + - `357036551735-compute@developer.gserviceaccount.com` + - `vps-ai@propane-will-491900-m5.iam.gserviceaccount.com` +- **Opprettet `osvxcc-sa`** med minimalt privilegier (`run.invoker`, `secretmanager.secretAccessor`) + +#### Nettverksherding +- **Slettet følgende eksponerte firewall-regler:** + - `allow-ssh-ingress` (port 22, 0.0.0.0/0) + - `default-allow-rdp` (port 3389, 0.0.0.0/0) + - `allow-ollama-11434` (port 11434, 0.0.0.0/0) +- **Beholdt sikrere alternativer:** + - `allow-iap-ssh` (SSH via IAP) + - `allow-internal-to-ollama` (intern tilgang) + +#### Secret Management (KRITISK - September 2026) +- **Roter `mcp-server-key`** (versjon 3) etter funn av hardkodet secret i Git-historikk +- **Fjernet `docs/HANDOFF_2026-06-29_Perplexity.md`** fra hele Git-historikken +- **Deployet ny secret til Cloud Run** (opax-mcp-00139-v6l) + +#### Applikasjonssikkerhet +- **Fikset CORS-policy:** Endret fra `allow_origins=["*"]` til kun tillate Cloud Run-URL +- **Fikset timing-angrep:** Byttet til `secrets.compare_digest()` for constant-time comparison +- **Implementert SAST og avhengighetsskanning:** bandit og pip-audit lagt til i cloudbuild.yaml + +### ⚠️ Identifiserte forbedringspunkter (To-Do) + +Dette er den aktive listen over kjente sikkerhetshull som må adresseres. + +| Hull | Status | Anbefaling | +|------|--------|------------| +| Sikkerhets-alerting (IAM-endringer) | Blokkert (miljøbegrensning) | Opprett manuelt via Cloud Console | +| Hemmelighetsrotasjon (automatisk) | Ikke konfigurert | Konfigurer rotasjon i Secret Manager (90 dager) | +| Sårbarhetsskanning (Container Analysis) | Blokkert (miljøbegrensning) | Aktiver via Cloud Console | +| Detaljert audit logging | Ikke startet | Aktiver Data Access logs for kritiske tjenester | + +### 📊 Sikkerhetsscore (Snapshot September 2026) + +| Kategori | Før | Etter | Status | +|----------|-----|-------|--------| +| IAM | 🔴 Kritisk | 🟢 God | ✅ Forbedret | +| Nettverk | 🔴 Kritisk | 🟢 God | ✅ Forbedret | +| Secret Management | 🔴 Kritisk | 🟢 God | ✅ Forbedret | +| Applikasjonssikkerhet | 🟠 Moderat | 🟢 God | ✅ Forbedret | +| CI/CD-sikkerhet | 🟠 Moderat | 🟢 God | ✅ Forbedret | +| Logging/Alerting | 🔴 Kritisk | 🟠 Moderat | ⚠️ Gjenstår | +| Container Security | 🟠 Moderat | 🟠 Moderat | ⚠️ Gjenstår | + +**Totalvurdering:** Arkitekturen er **betydelig forsterket**. Gjenstående arbeid er primært innenfor overvåking, logging og automatisering. diff --git a/final-secret-test.txt b/final-secret-test.txt deleted file mode 100644 index c3eba99..0000000 --- a/final-secret-test.txt +++ /dev/null @@ -1 +0,0 @@ -password = 'a-new-fake-password' \ No newline at end of file diff --git a/gitea-backup-candidate.sh b/gitea-backup-candidate.sh deleted file mode 100644 index 23d7b2f..0000000 --- a/gitea-backup-candidate.sh +++ /dev/null @@ -1,400 +0,0 @@ -#!/usr/bin/env bash -# Gitea application-consistent backup script. -# -# Operator-attestation basis: -# - No intentionally required nested mount boundaries exist beneath the approved -# source roots. -# - The SQLite database is captured with SQLite's online .backup mechanism. -# - Filesystem-tree copies are best effort while Gitea remains online. -# - This script does not perform runtime mount-boundary or nested-symlink scans. -# -# The script archives only a staged SQLite backup, not the live SQLite DB, -# WAL, SHM, or journal files. - -set -euo pipefail -IFS=$'\n\t' -umask 077 - -readonly APP_INI_PATH="/opt/gitea/data/custom/conf/app.ini" -readonly SQLITE_DB_PATH="/opt/gitea/data/data/gitea.db" -readonly REPO_PATH="/opt/gitea/data/repositories" -readonly LFS_PATH="/opt/gitea/data/lfs" -readonly ATTACHMENTS_PATH="/opt/gitea/data/data/attachments" -readonly AVATARS_PATH="/opt/gitea/data/data/avatars" -readonly PACKAGES_PATH="/opt/gitea/data/data/packages" -readonly REPO_ARCHIVE_PATH="/opt/gitea/data/data/repo-archive" -readonly REPO_AVATARS_PATH="/opt/gitea/data/data/repo-avatars" - -readonly GCS_BUCKET="gs://vauco-gitea-backups-20260901" -readonly GCS_PREFIX="manual" -readonly STAGING_DIR="/var/backups/gitea" -readonly LOCK_FILE="/var/run/gitea-backup.lock" -readonly LOG_FILE="/var/log/gitea-backup.log" -readonly REQUIRED_FREE_KB=646368 - -workspace="" -workspace_preserved=0 -log_ready=0 - -log_status() { - local phase="$1" - local status="$2" - local category="$3" - - if [ "${log_ready}" -eq 1 ]; then - printf '%s phase=%s status=%s category=%s\n' \ - "$(date -u --iso-8601=seconds)" \ - "${phase}" \ - "${status}" \ - "${category}" >> "${LOG_FILE}" - else - printf '%s\n' \ - "gitea-backup phase=${phase} status=${status} category=${category}" >&2 - fi -} - -fail_without_workspace() { - log_status "$1" "FAILURE" "$2" - exit 1 -} - -workspace_is_safe() { - [ -n "${workspace}" ] && - [ -d "${workspace}" ] && - [ ! -L "${workspace}" ] && - [ "$(dirname -- "${workspace}")" = "${STAGING_DIR}" ] && - [ "$(stat -c '%u:%g:%a' -- "${workspace}")" = "0:0:700" ] -} - -remove_workspace() { - workspace_is_safe || return 1 - rm -rf -- "${workspace}" -} - -preserve_workspace() { - workspace_is_safe || return 1 - - if find -- "${workspace}" -xdev -type l -print -quit 2>/dev/null | - grep -q . >/dev/null 2>&1; then - return 1 - fi - - if find -- "${workspace}" -xdev \ - ! -type d ! -type f ! -type l \ - -print -quit 2>/dev/null | - grep -q . >/dev/null 2>&1; then - return 1 - fi - - if ! find -- "${workspace}" -xdev -type d \ - -exec chmod 700 -- {} + >/dev/null 2>&1; then - return 1 - fi - - if ! find -- "${workspace}" -xdev -type f \ - -exec chmod 600 -- {} + >/dev/null 2>&1; then - return 1 - fi - - workspace_preserved=1 - trap - EXIT - return 0 -} - -fail_after_workspace() { - log_status "$1" "FAILURE" "$2" - - if ! preserve_workspace; then - log_status "PRESERVATION" "FAILURE" "WorkspacePreservationFailed" - fi - - exit 1 -} - -cleanup_on_exit() { - local rc=$? - - if [ "${workspace_preserved}" -eq 0 ]; then - remove_workspace >/dev/null 2>&1 || true - fi - - exit "${rc}" -} - -require_command() { - if ! command -v "$1" >/dev/null 2>&1; then - fail_without_workspace "PREFLIGHT" "RequiredCommandMissing" - fi -} - -validate_regular() { - if ! [ -f "$1" ] || [ -L "$1" ]; then - fail_without_workspace "PREFLIGHT" "$2" - fi -} - -validate_directory() { - if ! [ -d "$1" ] || [ -L "$1" ]; then - fail_without_workspace "PREFLIGHT" "$2" - fi -} - -validate_archive_manifest() { - local archive_path="$1" - - tar -tzf "${archive_path}" >/dev/null 2>&1 || return 1 - - tar -tf "${archive_path}" 2>/dev/null | - awk ' - BEGIN { - allowed["database"] = 1 - allowed["config"] = 1 - allowed["repositories"] = 1 - allowed["lfs"] = 1 - allowed["data"] = 1 - - required["database/"] = 0 - required["config/"] = 0 - required["repositories/"] = 0 - required["lfs/"] = 0 - required["data/"] = 0 - } - - /^\// { bad = 1; exit 1 } - /(^|\/)\.\.(\/|$)/ { bad = 1; exit 1 } - - { - member = $0 - split(member, parts, "/") - root = parts[1] - - if (!(root in allowed)) { - bad = 1 - exit 1 - } - - if (member in required) { - required[member] = 1 - } - } - - END { - if (bad) { - exit 1 - } - - for (entry in required) { - if (required[entry] != 1) { - exit 1 - } - } - }' -} - -copy_source() { - if ! cp -a -- "$1" "$2"; then - fail_after_workspace "STAGING" "$3" - fi -} - -main() { - local required_command="" - local archive_root="" - local run_id="" - local archive_name="" - local archive_path="" - local checksum_path="" - local archive_object="" - local checksum_object="" - - for required_command in \ - flock df awk mktemp mkdir sqlite3 cp tar sha256sum gcloud \ - stat find chmod rm dirname grep date printf; do - require_command "${required_command}" - done - - if ! [ -f "${LOG_FILE}" ] || [ -L "${LOG_FILE}" ]; then - printf '%s\n' \ - "gitea-backup phase=PREFLIGHT status=FAILURE category=LogFileInvalid" >&2 - exit 1 - fi - - if [ "$(stat -c '%u:%g:%a' -- "${LOG_FILE}")" != "0:0:600" ]; then - printf '%s\n' \ - "gitea-backup phase=PREFLIGHT status=FAILURE category=LogFilePermissionsInvalid" >&2 - exit 1 - fi - - if ! [ -w "${LOG_FILE}" ]; then - printf '%s\n' \ - "gitea-backup phase=PREFLIGHT status=FAILURE category=LogFileUnavailable" >&2 - exit 1 - fi - - log_ready=1 - log_status "PREFLIGHT" "START" "BackupJob" - - if ! [ -d "${STAGING_DIR}" ] || [ -L "${STAGING_DIR}" ]; then - fail_without_workspace "PREFLIGHT" "StagingDirectoryInvalid" - fi - - if [ "$(stat -c '%u:%g:%a' -- "${STAGING_DIR}")" != "0:0:700" ]; then - fail_without_workspace "PREFLIGHT" "StagingDirectoryPermissionsInvalid" - fi - - if ! [ -w "${STAGING_DIR}" ]; then - fail_without_workspace "PREFLIGHT" "StagingDirectoryUnavailable" - fi - - if ! df --output=avail -- "${STAGING_DIR}" 2>/dev/null | - awk -v required_kb="${REQUIRED_FREE_KB}" ' - NR == 2 { - checked = 1 - exit !($1 > required_kb) - } - END { - if (!checked) { - exit 1 - } - }'; then - fail_without_workspace "PREFLIGHT" "InsufficientStagingCapacity" - fi - - validate_regular "${APP_INI_PATH}" "AppIniInvalid" - validate_regular "${SQLITE_DB_PATH}" "SQLiteDatabaseInvalid" - - validate_directory "${REPO_PATH}" "RepositoriesInvalid" - validate_directory "${LFS_PATH}" "LfsInvalid" - validate_directory "${ATTACHMENTS_PATH}" "AttachmentsInvalid" - validate_directory "${AVATARS_PATH}" "AvatarsInvalid" - validate_directory "${PACKAGES_PATH}" "PackagesInvalid" - validate_directory "${REPO_ARCHIVE_PATH}" "RepositoryArchivesInvalid" - validate_directory "${REPO_AVATARS_PATH}" "RepositoryAvatarsInvalid" - - if ! workspace="$(mktemp -d -p "${STAGING_DIR}" "backup.XXXXXX")"; then - fail_without_workspace "PREFLIGHT" "WorkspaceCreationFailed" - fi - - if ! workspace_is_safe; then - fail_after_workspace "PREFLIGHT" "WorkspaceValidationFailed" - fi - - trap cleanup_on_exit EXIT - - archive_root="${workspace}/archive_root" - - if ! mkdir -p -- \ - "${archive_root}/database" \ - "${archive_root}/config" \ - "${archive_root}/data"; then - fail_after_workspace "STAGING" "ArchiveRootCreationFailed" - fi - - log_status "DB_COPY" "START" "SQLiteOnlineBackup" - if ! sqlite3 "${SQLITE_DB_PATH}" \ - ".backup '${archive_root}/database/gitea.db'"; then - fail_after_workspace "DB_COPY" "SQLiteBackupFailed" - fi - log_status "DB_COPY" "SUCCESS" "SQLiteOnlineBackupComplete" - - log_status "STAGING" "START" "CopyingData" - - copy_source "${APP_INI_PATH}" \ - "${archive_root}/config/app.ini" \ - "CopyAppIniFailed" - - copy_source "${REPO_PATH}" \ - "${archive_root}/repositories" \ - "CopyRepositoriesFailed" - - copy_source "${LFS_PATH}" \ - "${archive_root}/lfs" \ - "CopyLfsFailed" - - copy_source "${ATTACHMENTS_PATH}" \ - "${archive_root}/data/attachments" \ - "CopyAttachmentsFailed" - - copy_source "${AVATARS_PATH}" \ - "${archive_root}/data/avatars" \ - "CopyAvatarsFailed" - - copy_source "${PACKAGES_PATH}" \ - "${archive_root}/data/packages" \ - "CopyPackagesFailed" - - copy_source "${REPO_ARCHIVE_PATH}" \ - "${archive_root}/data/repo-archive" \ - "CopyRepositoryArchivesFailed" - - copy_source "${REPO_AVATARS_PATH}" \ - "${archive_root}/data/repo-avatars" \ - "CopyRepositoryAvatarsFailed" - - log_status "STAGING" "SUCCESS" "CopyComplete" - - run_id="${workspace##*/}" - archive_name="gitea-backup-${run_id}.tar.gz" - archive_path="${workspace}/${archive_name}" - checksum_path="${archive_path}.sha256" - archive_object="${GCS_BUCKET}/${GCS_PREFIX}/${archive_name}" - checksum_object="${GCS_BUCKET}/${GCS_PREFIX}/${archive_name}.sha256" - - log_status "ARCHIVE" "START" "TarCreation" - if ! tar -czf "${archive_path}" \ - -C "${archive_root}" \ - database config repositories lfs data; then - fail_after_workspace "ARCHIVE" "TarCreationFailed" - fi - - if ! validate_archive_manifest "${archive_path}"; then - fail_after_workspace "ARCHIVE" "ManifestInvalid" - fi - log_status "ARCHIVE" "SUCCESS" "ManifestValidated" - - if ! ( - cd -- "${workspace}" - sha256sum -b -- "${archive_name}" > "${checksum_path}" - ); then - fail_after_workspace "CHECKSUM" "ChecksumCreationFailed" - fi - log_status "CHECKSUM" "SUCCESS" "ChecksumGenerated" - - log_status "UPLOAD_ARCHIVE" "START" "GCS" - if ! gcloud storage cp \ - --if-generation-match=0 \ - --quiet \ - "${archive_path}" \ - "${archive_object}" >/dev/null 2>&1; then - fail_after_workspace "UPLOAD_ARCHIVE" "ArchiveUploadFailed" - fi - log_status "UPLOAD_ARCHIVE" "SUCCESS" "ArchiveUploadComplete" - - log_status "UPLOAD_CHECKSUM" "START" "GCS" - if ! gcloud storage cp \ - --if-generation-match=0 \ - --quiet \ - "${checksum_path}" \ - "${checksum_object}" >/dev/null 2>&1; then - fail_after_workspace "UPLOAD_CHECKSUM" "ChecksumUploadFailed" - fi - log_status "UPLOAD_CHECKSUM" "SUCCESS" "ChecksumUploadComplete" - - if ! remove_workspace; then - fail_after_workspace "CLEANUP" "WorkspaceRemovalFailed" - fi - - workspace="" - trap - EXIT - log_status "JOB" "SUCCESS" "BackupComplete" -} - -( - flock -n 200 || { - printf '%s\n' \ - "gitea-backup phase=PREFLIGHT status=FAILURE category=LockHeld" >&2 - exit 1 - } - - main -) 200>"${LOCK_FILE}" diff --git a/jonen publiserer b/jonen publiserer new file mode 100644 index 0000000..333a0b5 --- /dev/null +++ b/jonen publiserer @@ -0,0 +1,258 @@ + + SSUUMMMMAARRYY OOFF LLEESSSS CCOOMMMMAANNDDSS + + Commands marked with * may be preceded by a number, _N. + Notes in parentheses indicate the behavior if _N is given. + A key preceded by a caret indicates the Ctrl key; thus ^K is ctrl-K. + + h H Display this help. + q :q Q :Q ZZ Exit. + --------------------------------------------------------------------------- + + MMOOVVIINNGG + + e ^E j ^N CR * Forward one line (or _N lines). + y ^Y k ^K ^P * Backward one line (or _N lines). + f ^F ^V SPACE * Forward one window (or _N lines). + b ^B ESC-v * Backward one window (or _N lines). + z * Forward one window (and set window to _N). + w * Backward one window (and set window to _N). + ESC-SPACE * Forward one window, but don't stop at end-of-file. + d ^D * Forward one half-window (and set half-window to _N). + u ^U * Backward one half-window (and set half-window to _N). + ESC-) RightArrow * Right one half screen width (or _N positions). + ESC-( LeftArrow * Left one half screen width (or _N positions). + ESC-} ^RightArrow Right to last column displayed. + ESC-{ ^LeftArrow Left to first column. + F Forward forever; like "tail -f". + ESC-F Like F but stop when search pattern is found. + r ^R ^L Repaint screen. + R Repaint screen, discarding buffered input. + --------------------------------------------------- + Default "window" is the screen height. + Default "half-window" is half of the screen height. + --------------------------------------------------------------------------- + + SSEEAARRCCHHIINNGG + + /_p_a_t_t_e_r_n * Search forward for (_N-th) matching line. + ?_p_a_t_t_e_r_n * Search backward for (_N-th) matching line. + n * Repeat previous search (for _N-th occurrence). + N * Repeat previous search in reverse direction. + ESC-n * Repeat previous search, spanning files. + ESC-N * Repeat previous search, reverse dir. & spanning files. + ESC-u Undo (toggle) search highlighting. + ESC-U Clear search highlighting. + &_p_a_t_t_e_r_n * Display only matching lines. + --------------------------------------------------- + A search pattern may begin with one or more of: + ^N or ! Search for NON-matching lines. + ^E or * Search multiple files (pass thru END OF FILE). + ^F or @ Start search at FIRST file (for /) or last file (for ?). + ^K Highlight matches, but don't move (KEEP position). + ^R Don't use REGULAR EXPRESSIONS. + ^W WRAP search if no match found. + --------------------------------------------------------------------------- + + JJUUMMPPIINNGG + + g < ESC-< * Go to first line in file (or line _N). + G > ESC-> * Go to last line in file (or line _N). + p % * Go to beginning of file (or _N percent into file). + t * Go to the (_N-th) next tag. + T * Go to the (_N-th) previous tag. + { ( [ * Find close bracket } ) ]. + } ) ] * Find open bracket { ( [. + ESC-^F _<_c_1_> _<_c_2_> * Find close bracket _<_c_2_>. + ESC-^B _<_c_1_> _<_c_2_> * Find open bracket _<_c_1_>. + --------------------------------------------------- + Each "find close bracket" command goes forward to the close bracket + matching the (_N-th) open bracket in the top line. + Each "find open bracket" command goes backward to the open bracket + matching the (_N-th) close bracket in the bottom line. + + m_<_l_e_t_t_e_r_> Mark the current top line with . + M_<_l_e_t_t_e_r_> Mark the current bottom line with . + '_<_l_e_t_t_e_r_> Go to a previously marked position. + '' Go to the previous position. + ^X^X Same as '. + ESC-M_<_l_e_t_t_e_r_> Clear a mark. + --------------------------------------------------- + A mark is any upper-case or lower-case letter. + Certain marks are predefined: + ^ means beginning of the file + $ means end of the file + --------------------------------------------------------------------------- + + CCHHAANNGGIINNGG FFIILLEESS + + :e [_f_i_l_e] Examine a new file. + ^X^V Same as :e. + :n * Examine the (_N-th) next file from the command line. + :p * Examine the (_N-th) previous file from the command line. + :x * Examine the first (or _N-th) file from the command line. + :d Delete the current file from the command line list. + = ^G :f Print current file name. + --------------------------------------------------------------------------- + + MMIISSCCEELLLLAANNEEOOUUSS CCOOMMMMAANNDDSS + + -_<_f_l_a_g_> Toggle a command line option [see OPTIONS below]. + --_<_n_a_m_e_> Toggle a command line option, by name. + __<_f_l_a_g_> Display the setting of a command line option. + ___<_n_a_m_e_> Display the setting of an option, by name. + +_c_m_d Execute the less cmd each time a new file is examined. + + !_c_o_m_m_a_n_d Execute the shell command with $SHELL. + |XX_c_o_m_m_a_n_d Pipe file between current pos & mark XX to shell command. + s _f_i_l_e Save input to a file. + v Edit the current file with $VISUAL or $EDITOR. + V Print version number of "less". + --------------------------------------------------------------------------- + + OOPPTTIIOONNSS + + Most options may be changed either on the command line, + or from within less by using the - or -- command. + Options may be given in one of two forms: either a single + character preceded by a -, or a name preceded by --. + + -? ........ --help + Display help (from command line). + -a ........ --search-skip-screen + Search skips current screen. + -A ........ --SEARCH-SKIP-SCREEN + Search starts just after target line. + -b [_N] .... --buffers=[_N] + Number of buffers. + -B ........ --auto-buffers + Don't automatically allocate buffers for pipes. + -c ........ --clear-screen + Repaint by clearing rather than scrolling. + -d ........ --dumb + Dumb terminal. + -D xx_c_o_l_o_r . --color=xx_c_o_l_o_r + Set screen colors. + -e -E .... --quit-at-eof --QUIT-AT-EOF + Quit at end of file. + -f ........ --force + Force open non-regular files. + -F ........ --quit-if-one-screen + Quit if entire file fits on first screen. + -g ........ --hilite-search + Highlight only last match for searches. + -G ........ --HILITE-SEARCH + Don't highlight any matches for searches. + -h [_N] .... --max-back-scroll=[_N] + Backward scroll limit. + -i ........ --ignore-case + Ignore case in searches that do not contain uppercase. + -I ........ --IGNORE-CASE + Ignore case in all searches. + -j [_N] .... --jump-target=[_N] + Screen position of target lines. + -J ........ --status-column + Display a status column at left edge of screen. + -k [_f_i_l_e] . --lesskey-file=[_f_i_l_e] + Use a lesskey file. + -K ........ --quit-on-intr + Exit less in response to ctrl-C. + -L ........ --no-lessopen + Ignore the LESSOPEN environment variable. + -m -M .... --long-prompt --LONG-PROMPT + Set prompt style. + -n -N .... --line-numbers --LINE-NUMBERS + Don't use line numbers. + -o [_f_i_l_e] . --log-file=[_f_i_l_e] + Copy to log file (standard input only). + -O [_f_i_l_e] . --LOG-FILE=[_f_i_l_e] + Copy to log file (unconditionally overwrite). + -p [_p_a_t_t_e_r_n] --pattern=[_p_a_t_t_e_r_n] + Start at pattern (from command line). + -P [_p_r_o_m_p_t] --prompt=[_p_r_o_m_p_t] + Define new prompt. + -q -Q .... --quiet --QUIET --silent --SILENT + Quiet the terminal bell. + -r -R .... --raw-control-chars --RAW-CONTROL-CHARS + Output "raw" control characters. + -s ........ --squeeze-blank-lines + Squeeze multiple blank lines. + -S ........ --chop-long-lines + Chop (truncate) long lines rather than wrapping. + -t [_t_a_g] .. --tag=[_t_a_g] + Find a tag. + -T [_t_a_g_s_f_i_l_e] --tag-file=[_t_a_g_s_f_i_l_e] + Use an alternate tags file. + -u -U .... --underline-special --UNDERLINE-SPECIAL + Change handling of backspaces. + -V ........ --version + Display the version number of "less". + -w ........ --hilite-unread + Highlight first new line after forward-screen. + -W ........ --HILITE-UNREAD + Highlight first new line after any forward movement. + -x [_N[,...]] --tabs=[_N[,...]] + Set tab stops. + -X ........ --no-init + Don't use termcap init/deinit strings. + -y [_N] .... --max-forw-scroll=[_N] + Forward scroll limit. + -z [_N] .... --window=[_N] + Set size of window. + -" [_c[_c]] . --quotes=[_c[_c]] + Set shell quote characters. + -~ ........ --tilde + Don't display tildes after end of file. + -# [_N] .... --shift=[_N] + Set horizontal scroll amount (0 = one half screen width). + --file-size + Automatically determine the size of the input file. + --follow-name + The F command changes files if the input file is renamed. + --incsearch + Search file as each pattern character is typed in. + --line-num-width=N + Set the width of the -N line number field to N characters. + --mouse + Enable mouse input. + --no-keypad + Don't send termcap keypad init/deinit strings. + --no-histdups + Remove duplicates from command history. + --rscroll=C + Set the character used to mark truncated lines. + --save-marks + Retain marks across invocations of less. + --status-col-width=N + Set the width of the -J status column to N characters. + --use-backslash + Subsequent options use backslash as escape char. + --use-color + Enables colored text. + --wheel-lines=N + Each click of the mouse wheel moves N lines. + + + --------------------------------------------------------------------------- + + LLIINNEE EEDDIITTIINNGG + + These keys can be used to edit text being entered + on the "command line" at the bottom of the screen. + + RightArrow ..................... ESC-l ... Move cursor right one character. + LeftArrow ...................... ESC-h ... Move cursor left one character. + ctrl-RightArrow ESC-RightArrow ESC-w ... Move cursor right one word. + ctrl-LeftArrow ESC-LeftArrow ESC-b ... Move cursor left one word. + HOME ........................... ESC-0 ... Move cursor to start of line. + END ............................ ESC-$ ... Move cursor to end of line. + BACKSPACE ................................ Delete char to left of cursor. + DELETE ......................... ESC-x ... Delete char under cursor. + ctrl-BACKSPACE ESC-BACKSPACE ........... Delete word to left of cursor. + ctrl-DELETE .... ESC-DELETE .... ESC-X ... Delete word under cursor. + ctrl-U ......... ESC (MS-DOS only) ....... Delete entire line. + UpArrow ........................ ESC-k ... Retrieve previous command line. + DownArrow ...................... ESC-j ... Retrieve next command line. + TAB ...................................... Complete filename & cycle. + SHIFT-TAB ...................... ESC-TAB Complete filename & reverse cycle. + ctrl-L ................................... Complete filename, list all. diff --git a/k lyttes på b/k lyttes på new file mode 100644 index 0000000..333a0b5 --- /dev/null +++ b/k lyttes på @@ -0,0 +1,258 @@ + + SSUUMMMMAARRYY OOFF LLEESSSS CCOOMMMMAANNDDSS + + Commands marked with * may be preceded by a number, _N. + Notes in parentheses indicate the behavior if _N is given. + A key preceded by a caret indicates the Ctrl key; thus ^K is ctrl-K. + + h H Display this help. + q :q Q :Q ZZ Exit. + --------------------------------------------------------------------------- + + MMOOVVIINNGG + + e ^E j ^N CR * Forward one line (or _N lines). + y ^Y k ^K ^P * Backward one line (or _N lines). + f ^F ^V SPACE * Forward one window (or _N lines). + b ^B ESC-v * Backward one window (or _N lines). + z * Forward one window (and set window to _N). + w * Backward one window (and set window to _N). + ESC-SPACE * Forward one window, but don't stop at end-of-file. + d ^D * Forward one half-window (and set half-window to _N). + u ^U * Backward one half-window (and set half-window to _N). + ESC-) RightArrow * Right one half screen width (or _N positions). + ESC-( LeftArrow * Left one half screen width (or _N positions). + ESC-} ^RightArrow Right to last column displayed. + ESC-{ ^LeftArrow Left to first column. + F Forward forever; like "tail -f". + ESC-F Like F but stop when search pattern is found. + r ^R ^L Repaint screen. + R Repaint screen, discarding buffered input. + --------------------------------------------------- + Default "window" is the screen height. + Default "half-window" is half of the screen height. + --------------------------------------------------------------------------- + + SSEEAARRCCHHIINNGG + + /_p_a_t_t_e_r_n * Search forward for (_N-th) matching line. + ?_p_a_t_t_e_r_n * Search backward for (_N-th) matching line. + n * Repeat previous search (for _N-th occurrence). + N * Repeat previous search in reverse direction. + ESC-n * Repeat previous search, spanning files. + ESC-N * Repeat previous search, reverse dir. & spanning files. + ESC-u Undo (toggle) search highlighting. + ESC-U Clear search highlighting. + &_p_a_t_t_e_r_n * Display only matching lines. + --------------------------------------------------- + A search pattern may begin with one or more of: + ^N or ! Search for NON-matching lines. + ^E or * Search multiple files (pass thru END OF FILE). + ^F or @ Start search at FIRST file (for /) or last file (for ?). + ^K Highlight matches, but don't move (KEEP position). + ^R Don't use REGULAR EXPRESSIONS. + ^W WRAP search if no match found. + --------------------------------------------------------------------------- + + JJUUMMPPIINNGG + + g < ESC-< * Go to first line in file (or line _N). + G > ESC-> * Go to last line in file (or line _N). + p % * Go to beginning of file (or _N percent into file). + t * Go to the (_N-th) next tag. + T * Go to the (_N-th) previous tag. + { ( [ * Find close bracket } ) ]. + } ) ] * Find open bracket { ( [. + ESC-^F _<_c_1_> _<_c_2_> * Find close bracket _<_c_2_>. + ESC-^B _<_c_1_> _<_c_2_> * Find open bracket _<_c_1_>. + --------------------------------------------------- + Each "find close bracket" command goes forward to the close bracket + matching the (_N-th) open bracket in the top line. + Each "find open bracket" command goes backward to the open bracket + matching the (_N-th) close bracket in the bottom line. + + m_<_l_e_t_t_e_r_> Mark the current top line with . + M_<_l_e_t_t_e_r_> Mark the current bottom line with . + '_<_l_e_t_t_e_r_> Go to a previously marked position. + '' Go to the previous position. + ^X^X Same as '. + ESC-M_<_l_e_t_t_e_r_> Clear a mark. + --------------------------------------------------- + A mark is any upper-case or lower-case letter. + Certain marks are predefined: + ^ means beginning of the file + $ means end of the file + --------------------------------------------------------------------------- + + CCHHAANNGGIINNGG FFIILLEESS + + :e [_f_i_l_e] Examine a new file. + ^X^V Same as :e. + :n * Examine the (_N-th) next file from the command line. + :p * Examine the (_N-th) previous file from the command line. + :x * Examine the first (or _N-th) file from the command line. + :d Delete the current file from the command line list. + = ^G :f Print current file name. + --------------------------------------------------------------------------- + + MMIISSCCEELLLLAANNEEOOUUSS CCOOMMMMAANNDDSS + + -_<_f_l_a_g_> Toggle a command line option [see OPTIONS below]. + --_<_n_a_m_e_> Toggle a command line option, by name. + __<_f_l_a_g_> Display the setting of a command line option. + ___<_n_a_m_e_> Display the setting of an option, by name. + +_c_m_d Execute the less cmd each time a new file is examined. + + !_c_o_m_m_a_n_d Execute the shell command with $SHELL. + |XX_c_o_m_m_a_n_d Pipe file between current pos & mark XX to shell command. + s _f_i_l_e Save input to a file. + v Edit the current file with $VISUAL or $EDITOR. + V Print version number of "less". + --------------------------------------------------------------------------- + + OOPPTTIIOONNSS + + Most options may be changed either on the command line, + or from within less by using the - or -- command. + Options may be given in one of two forms: either a single + character preceded by a -, or a name preceded by --. + + -? ........ --help + Display help (from command line). + -a ........ --search-skip-screen + Search skips current screen. + -A ........ --SEARCH-SKIP-SCREEN + Search starts just after target line. + -b [_N] .... --buffers=[_N] + Number of buffers. + -B ........ --auto-buffers + Don't automatically allocate buffers for pipes. + -c ........ --clear-screen + Repaint by clearing rather than scrolling. + -d ........ --dumb + Dumb terminal. + -D xx_c_o_l_o_r . --color=xx_c_o_l_o_r + Set screen colors. + -e -E .... --quit-at-eof --QUIT-AT-EOF + Quit at end of file. + -f ........ --force + Force open non-regular files. + -F ........ --quit-if-one-screen + Quit if entire file fits on first screen. + -g ........ --hilite-search + Highlight only last match for searches. + -G ........ --HILITE-SEARCH + Don't highlight any matches for searches. + -h [_N] .... --max-back-scroll=[_N] + Backward scroll limit. + -i ........ --ignore-case + Ignore case in searches that do not contain uppercase. + -I ........ --IGNORE-CASE + Ignore case in all searches. + -j [_N] .... --jump-target=[_N] + Screen position of target lines. + -J ........ --status-column + Display a status column at left edge of screen. + -k [_f_i_l_e] . --lesskey-file=[_f_i_l_e] + Use a lesskey file. + -K ........ --quit-on-intr + Exit less in response to ctrl-C. + -L ........ --no-lessopen + Ignore the LESSOPEN environment variable. + -m -M .... --long-prompt --LONG-PROMPT + Set prompt style. + -n -N .... --line-numbers --LINE-NUMBERS + Don't use line numbers. + -o [_f_i_l_e] . --log-file=[_f_i_l_e] + Copy to log file (standard input only). + -O [_f_i_l_e] . --LOG-FILE=[_f_i_l_e] + Copy to log file (unconditionally overwrite). + -p [_p_a_t_t_e_r_n] --pattern=[_p_a_t_t_e_r_n] + Start at pattern (from command line). + -P [_p_r_o_m_p_t] --prompt=[_p_r_o_m_p_t] + Define new prompt. + -q -Q .... --quiet --QUIET --silent --SILENT + Quiet the terminal bell. + -r -R .... --raw-control-chars --RAW-CONTROL-CHARS + Output "raw" control characters. + -s ........ --squeeze-blank-lines + Squeeze multiple blank lines. + -S ........ --chop-long-lines + Chop (truncate) long lines rather than wrapping. + -t [_t_a_g] .. --tag=[_t_a_g] + Find a tag. + -T [_t_a_g_s_f_i_l_e] --tag-file=[_t_a_g_s_f_i_l_e] + Use an alternate tags file. + -u -U .... --underline-special --UNDERLINE-SPECIAL + Change handling of backspaces. + -V ........ --version + Display the version number of "less". + -w ........ --hilite-unread + Highlight first new line after forward-screen. + -W ........ --HILITE-UNREAD + Highlight first new line after any forward movement. + -x [_N[,...]] --tabs=[_N[,...]] + Set tab stops. + -X ........ --no-init + Don't use termcap init/deinit strings. + -y [_N] .... --max-forw-scroll=[_N] + Forward scroll limit. + -z [_N] .... --window=[_N] + Set size of window. + -" [_c[_c]] . --quotes=[_c[_c]] + Set shell quote characters. + -~ ........ --tilde + Don't display tildes after end of file. + -# [_N] .... --shift=[_N] + Set horizontal scroll amount (0 = one half screen width). + --file-size + Automatically determine the size of the input file. + --follow-name + The F command changes files if the input file is renamed. + --incsearch + Search file as each pattern character is typed in. + --line-num-width=N + Set the width of the -N line number field to N characters. + --mouse + Enable mouse input. + --no-keypad + Don't send termcap keypad init/deinit strings. + --no-histdups + Remove duplicates from command history. + --rscroll=C + Set the character used to mark truncated lines. + --save-marks + Retain marks across invocations of less. + --status-col-width=N + Set the width of the -J status column to N characters. + --use-backslash + Subsequent options use backslash as escape char. + --use-color + Enables colored text. + --wheel-lines=N + Each click of the mouse wheel moves N lines. + + + --------------------------------------------------------------------------- + + LLIINNEE EEDDIITTIINNGG + + These keys can be used to edit text being entered + on the "command line" at the bottom of the screen. + + RightArrow ..................... ESC-l ... Move cursor right one character. + LeftArrow ...................... ESC-h ... Move cursor left one character. + ctrl-RightArrow ESC-RightArrow ESC-w ... Move cursor right one word. + ctrl-LeftArrow ESC-LeftArrow ESC-b ... Move cursor left one word. + HOME ........................... ESC-0 ... Move cursor to start of line. + END ............................ ESC-$ ... Move cursor to end of line. + BACKSPACE ................................ Delete char to left of cursor. + DELETE ......................... ESC-x ... Delete char under cursor. + ctrl-BACKSPACE ESC-BACKSPACE ........... Delete word to left of cursor. + ctrl-DELETE .... ESC-DELETE .... ESC-X ... Delete word under cursor. + ctrl-U ......... ESC (MS-DOS only) ....... Delete entire line. + UpArrow ........................ ESC-k ... Retrieve previous command line. + DownArrow ...................... ESC-j ... Retrieve next command line. + TAB ...................................... Complete filename & cycle. + SHIFT-TAB ...................... ESC-TAB Complete filename & reverse cycle. + ctrl-L ................................... Complete filename, list all. diff --git a/opax-mcp/server.py b/opax-mcp/server.py index fe94efe..b732ce3 100644 --- a/opax-mcp/server.py +++ b/opax-mcp/server.py @@ -17,6 +17,7 @@ from fastapi import FastAPI, Request, HTTPException from fastapi.responses import JSONResponse, StreamingResponse from fastapi.middleware.cors import CORSMiddleware import asyncio +import secrets # --- Tool Imports (with fault tolerance) --- try: @@ -72,7 +73,7 @@ app = FastAPI(title="opax-mcp", version="3.6.0") # BUMPED app.add_middleware( CORSMiddleware, - allow_origins=["*"], + allow_origins=["https://opax-mcp-357036551735.us-central1.run.app"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], @@ -135,7 +136,7 @@ async def _verify_auth(request: Request) -> None: if auth_header.startswith("Bearer "): token = auth_header[7:] - if not token or token != secret: + if not token or not secrets.compare_digest(token.encode(), secret.encode()): raise HTTPException(status_code=401, detail="Invalid or missing API key") diff --git a/remove_cron_debug.sh b/remove_cron_debug.sh deleted file mode 100755 index bae7fcf..0000000 --- a/remove_cron_debug.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash -set -euo pipefail - -BACKUP="$HOME/crontab.backup.$(date +%F-%H%M%S)" -crontab -l > "$BACKUP" - -TMP="$(mktemp)" -grep -vE 'OSVauco/scripts/vm-teardown\.sh|secret=webhook-url.*(teardown|VM kjører)' "$BACKUP" > "$TMP" - -echo "--- Content of temp file ---" -cat "$TMP" -echo "---------------------------" - -crontab "$TMP" -# rm -f "$TMP" # Keep the temp file for debugging - -echo "Backup saved: $BACKUP" -echo -echo "Resulting crontab:" -crontab -l | nl -ba - -echo -echo "Verification:" -if crontab -l | grep -Ei 'vm-teardown\.sh|teardown|VM kjører'; then - echo "ERROR: A related entry is still present; do not make further changes automatically." - exit 1 -else - echo "OK: No VM teardown or teardown-warning cron entries remain." -fi diff --git a/remove_cron_debug2.sh b/remove_cron_debug2.sh deleted file mode 100755 index 4225ccf..0000000 --- a/remove_cron_debug2.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/bash -# set -euo pipefail - -exec > ~/debug_output.log 2>&1 - -BACKUP="$HOME/crontab.backup.$(date +%F-%H%M%S)" -crontab -l > "$BACKUP" - -TMP="$(mktemp)" -grep -vE 'OSVauco/scripts/vm-teardown\.sh|secret=webhook-url.*(teardown|VM kjører)' "$BACKUP" > "$TMP" - -echo "--- Content of temp file ---" -cat "$TMP" -echo "---------------------------" - -crontab "$TMP" -# rm -f "$TMP" # Keep the temp file for debugging - -echo "Backup saved: $BACKUP" -echo -echo "Resulting crontab:" -crontab -l | nl -ba - -echo -echo "Verification:" -if crontab -l | grep -Ei 'vm-teardown\.sh|teardown|VM kjører'; then - echo "ERROR: A related entry is still present; do not make further changes automatically." - exit 1 -else - echo "OK: No VM teardown or teardown-warning cron entries remain." -fi diff --git a/remove_cron_debug3.sh b/remove_cron_debug3.sh deleted file mode 100755 index 17fb3fd..0000000 --- a/remove_cron_debug3.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/bash -# set -euo pipefail - -exec > ~/OSVauco/debug_output.log 2>&1 - -BACKUP="$HOME/crontab.backup.$(date +%F-%H%M%S)" -crontab -l > "$BACKUP" - -TMP="$(mktemp)" -grep -vE 'OSVauco/scripts/vm-teardown\.sh|secret=webhook-url.*(teardown|VM kjører)' "$BACKUP" > "$TMP" - -echo "--- Content of temp file ---" -cat "$TMP" -echo "---------------------------" - -crontab "$TMP" -# rm -f "$TMP" # Keep the temp file for debugging - -echo "Backup saved: $BACKUP" -echo -echo "Resulting crontab:" -crontab -l | nl -ba - -echo -echo "Verification:" -if crontab -l | grep -Ei 'vm-teardown\.sh|teardown|VM kjører'; then - echo "ERROR: A related entry is still present; do not make further changes automatically." - exit 1 -else - echo "OK: No VM teardown or teardown-warning cron entries remain." -fi diff --git a/remove_cron_debug4.sh b/remove_cron_debug4.sh deleted file mode 100755 index a672808..0000000 --- a/remove_cron_debug4.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/bash -# set -euo pipefail - -exec > ~/OSVauco/debug_output.txt 2>&1 - -BACKUP="$HOME/crontab.backup.$(date +%F-%H%M%S)" -crontab -l > "$BACKUP" - -TMP="$(mktemp)" -grep -vE 'OSVauco/scripts/vm-teardown\.sh|secret=webhook-url.*(teardown|VM kjører)' "$BACKUP" > "$TMP" - -echo "--- Content of temp file ---" -cat "$TMP" -echo "---------------------------" - -crontab "$TMP" -# rm -f "$TMP" # Keep the temp file for debugging - -echo "Backup saved: $BACKUP" -echo -echo "Resulting crontab:" -crontab -l | nl -ba - -echo -echo "Verification:" -if crontab -l | grep -Ei 'vm-teardown\.sh|teardown|VM kjører'; then - echo "ERROR: A related entry is still present; do not make further changes automatically." - exit 1 -else - echo "OK: No VM teardown or teardown-warning cron entries remain." -fi diff --git a/test_secret.txt b/test_secret.txt deleted file mode 100644 index 259ab19..0000000 --- a/test_secret.txt +++ /dev/null @@ -1 +0,0 @@ -api_key="sk-12345abcdef" \ No newline at end of file diff --git a/toolcheck-20260721-113835.txt b/toolcheck-20260721-113835.txt deleted file mode 100644 index 2e96b15..0000000 --- a/toolcheck-20260721-113835.txt +++ /dev/null @@ -1,33 +0,0 @@ -=== get_connector_status === -{"jsonrpc":"2.0","id":1,"result":{"meta":{"status":"OPAX-MCP: Live\nGitea: Offline\nTools: 37 tilgjengelig","next_action":"Klar for kommandoer.","choices":[{"id":"list_tools","label":"List alle verktøy","style":"primary"}]},"content":[{"type":"text","text":"{\"opax_mcp_status\": \"live\", \"gitea_status\": \"offline\", \"tool_count\": 37}"}]}} - -=== get_billing_summary === -{"jsonrpc":"2.0","id":1,"result":{"meta":{"status":"Vellykket.","next_action":"Se over data og fortsett.","choices":[{"id":"continue","label":"Fortsett","style":"primary"}]},"content":[{"type":"text","text":"{\"summary\": [{\"usage_date\": \"2026-07-16\", \"project_id\": \"osv-mcp-m1-medioteq\", \"service\": \"Cloud Logging\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-13\", \"project_id\": \"osv-mcp-m1-medioteq\", \"service\": \"Cloud Logging\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-12\", \"project_id\": \"osv-mcp-m1-medioteq\", \"service\": \"Cloud Logging\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-11\", \"project_id\": \"osv-mcp-m1-medioteq\", \"service\": \"Cloud Logging\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-10\", \"project_id\": \"osv-mcp-m1-medioteq\", \"service\": \"Cloud Logging\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-09\", \"project_id\": \"osv-mcp-m1-medioteq\", \"service\": \"Cloud Logging\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-08\", \"project_id\": \"osv-mcp-m1-medioteq\", \"service\": \"Cloud Logging\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-07\", \"project_id\": \"osv-mcp-m1-medioteq\", \"service\": \"Cloud Logging\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-06\", \"project_id\": \"osv-mcp-m1-medioteq\", \"service\": \"Cloud Logging\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-05\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Compute Engine\", \"daily_cost\": 1.7300059999999993}, {\"usage_date\": \"2026-07-05\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Networking\", \"daily_cost\": 1.5778239999999997}, {\"usage_date\": \"2026-07-05\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Artifact Registry\", \"daily_cost\": 0.371965}, {\"usage_date\": \"2026-07-05\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Cloud DNS\", \"daily_cost\": 0.007977}, {\"usage_date\": \"2026-07-05\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"BigQuery\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-05\", \"project_id\": \"osv-mcp-m1-medioteq\", \"service\": \"Cloud Logging\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-05\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Cloud Logging\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-05\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Secret Manager\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-05\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Vertex AI Search\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-05\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Dataplex\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-04\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Compute Engine\", \"daily_cost\": 139.42401999999996}, {\"usage_date\": \"2026-07-04\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Networking\", \"daily_cost\": 13.146761000000012}, {\"usage_date\": \"2026-07-04\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Artifact Registry\", \"daily_cost\": 2.975715}, {\"usage_date\": \"2026-07-04\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Cloud DNS\", \"daily_cost\": 0.06381600000000001}, {\"usage_date\": \"2026-07-04\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Cloud Storage\", \"daily_cost\": 0.000929}, {\"usage_date\": \"2026-07-04\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Dataplex\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-04\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Vertex AI Search\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-04\", \"project_id\": \"osv-mcp-m1-medioteq\", \"service\": \"Cloud Logging\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-04\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Cloud Logging\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-04\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"BigQuery\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-04\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"App Engine\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-04\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"VM Manager\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-04\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Secret Manager\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-03\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Compute Engine\", \"daily_cost\": 190.26737499999996}, {\"usage_date\": \"2026-07-03\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Networking\", \"daily_cost\": 13.306756000000007}, {\"usage_date\": \"2026-07-03\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Artifact Registry\", \"daily_cost\": 2.9757239999999987}, {\"usage_date\": \"2026-07-03\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Cloud DNS\", \"daily_cost\": 0.06381600000000001}, {\"usage_date\": \"2026-07-03\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Cloud Storage\", \"daily_cost\": 0.000929}, {\"usage_date\": \"2026-07-03\", \"project_id\": \"osv-mcp-m1-medioteq\", \"service\": \"Cloud Logging\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-03\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"BigQuery\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-03\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Dataplex\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-03\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"VM Manager\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-03\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"App Engine\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-03\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Vertex AI Search\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-03\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Cloud Logging\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-03\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Secret Manager\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-02\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Vertex AI\", \"daily_cost\": 272.7504740000001}, {\"usage_date\": \"2026-07-02\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Compute Engine\", \"daily_cost\": 198.34232299999985}, {\"usage_date\": \"2026-07-02\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Networking\", \"daily_cost\": 13.361321000000006}, {\"usage_date\": \"2026-07-02\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Cloud Run\", \"daily_cost\": 4.526037}, {\"usage_date\": \"2026-07-02\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Artifact Registry\", \"daily_cost\": 2.9188569999999987}, {\"usage_date\": \"2026-07-02\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Cloud DNS\", \"daily_cost\": 0.06381600000000001}, {\"usage_date\": \"2026-07-02\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Cloud Build\", \"daily_cost\": 0.020162}, {\"usage_date\": \"2026-07-02\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Cloud Storage\", \"daily_cost\": 0.0028809999999999994}, {\"usage_date\": \"2026-07-02\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"BigQuery\", \"daily_cost\": 0.000709}, {\"usage_date\": \"2026-07-02\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Secret Manager\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-02\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Dataplex\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-02\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Cloud Monitoring\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-02\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"App Engine\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-02\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Cloud Logging\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-02\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Vertex AI Search\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-02\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"VM Manager\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-02\", \"project_id\": \"osv-mcp-m1-medioteq\", \"service\": \"Cloud Logging\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-01\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Compute Engine\", \"daily_cost\": 56.24774699999997}, {\"usage_date\": \"2026-07-01\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Vertex AI\", \"daily_cost\": 44.287913}, {\"usage_date\": \"2026-07-01\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Networking\", \"daily_cost\": 12.657937000000008}, {\"usage_date\": \"2026-07-01\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Artifact Registry\", \"daily_cost\": 2.3145819999999997}, {\"usage_date\": \"2026-07-01\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Cloud Run\", \"daily_cost\": 0.848989}, {\"usage_date\": \"2026-07-01\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Secret Manager\", \"daily_cost\": 0.22162199999999999}, {\"usage_date\": \"2026-07-01\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Cloud DNS\", \"daily_cost\": 0.06322100000000001}, {\"usage_date\": \"2026-07-01\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Cloud Storage\", \"daily_cost\": 0.007441999999999999}, {\"usage_date\": \"2026-07-01\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Cloud Build\", \"daily_cost\": 0.003186}, {\"usage_date\": \"2026-07-01\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"BigQuery\", \"daily_cost\": 0.000646}, {\"usage_date\": \"2026-07-01\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Dataplex\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-01\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"App Engine\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-01\", \"project_id\": \"osv-mcp-m1-medioteq\", \"service\": \"Cloud Logging\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-01\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Cloud Logging\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-01\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Cloud Monitoring\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-01\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Vertex AI Search\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-07-01\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"VM Manager\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-06-30\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Vertex AI\", \"daily_cost\": 121.69889600000002}, {\"usage_date\": \"2026-06-30\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Compute Engine\", \"daily_cost\": 53.09511500000003}, {\"usage_date\": \"2026-06-30\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Networking\", \"daily_cost\": 11.943491000000005}, {\"usage_date\": \"2026-06-30\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Artifact Registry\", \"daily_cost\": 3.3159909999999995}, {\"usage_date\": \"2026-06-30\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Cloud Run\", \"daily_cost\": 2.688987}, {\"usage_date\": \"2026-06-30\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Secret Manager\", \"daily_cost\": 0.7548699999999999}, {\"usage_date\": \"2026-06-30\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Cloud DNS\", \"daily_cost\": 0.061776}, {\"usage_date\": \"2026-06-30\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Cloud Build\", \"daily_cost\": 0.012364}, {\"usage_date\": \"2026-06-30\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Cloud Storage\", \"daily_cost\": 0.008996}, {\"usage_date\": \"2026-06-30\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"BigQuery\", \"daily_cost\": 0.0005219999999999999}, {\"usage_date\": \"2026-06-30\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Cloud Monitoring\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-06-30\", \"project_id\": \"osv-mcp-m1-medioteq\", \"service\": \"Cloud Logging\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-06-30\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Dataplex\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-06-30\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Vertex AI Search\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-06-30\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Cloud Logging\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-06-30\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"App Engine\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-06-30\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"VM Manager\", \"daily_cost\": 0.0}, {\"usage_date\": \"2026-06-29\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Compute Engine\", \"daily_cost\": 238.53166800000008}, {\"usage_date\": \"2026-06-29\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Vertex AI\", \"daily_cost\": 103.549366}, {\"usage_date\": \"2026-06-29\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Networking\", \"daily_cost\": 11.779823999999996}, {\"usage_date\": \"2026-06-29\", \"project_id\": \"propane-will-491900-m5\", \"service\": \"Cloud Run\", \"daily_cost\": 3.1022080000000005}]}"}]}} - -=== get_health === -{"jsonrpc":"2.0","id":1,"result":{"meta":{"status":"Vellykket.","next_action":"Se over data og fortsett.","choices":[{"id":"continue","label":"Fortsett","style":"primary"}]},"content":[{"type":"text","text":"{\"status\": \"ok\"}"}]}} - -=== get_build_status === -{"jsonrpc":"2.0","id":1,"result":{"meta":{"status":"Vellykket.","next_action":"Se over data og fortsett.","choices":[{"id":"continue","label":"Fortsett","style":"primary"}]},"content":[{"type":"text","text":"{\"builds\": [{\"id\": \"65330be7-0a51-408d-b9f6-b995545621e8\", \"status\": \"SUCCESS\", \"branch\": \"\", \"createTime\": \"2026-07-21T10:54:55.570121371Z\"}, {\"id\": \"8bf93c75-ce92-4320-ac73-93fc6da57318\", \"status\": \"SUCCESS\", \"branch\": \"\", \"createTime\": \"2026-07-21T10:35:24.806771139Z\"}, {\"id\": \"9a812dfc-b693-4635-8d57-45b7151deb4d\", \"status\": \"SUCCESS\", \"branch\": \"\", \"createTime\": \"2026-07-21T10:25:07.444060058Z\"}, {\"id\": \"16b46e3d-ce5d-47dd-90b0-d50d148440d0\", \"status\": \"SUCCESS\", \"branch\": \"\", \"createTime\": \"2026-07-21T10:07:55.277027971Z\"}, {\"id\": \"aa3573e6-caa4-4c7b-b4cd-2f3712f5c0f7\", \"status\": \"SUCCESS\", \"branch\": \"\", \"createTime\": \"2026-07-21T09:53:35.092989903Z\"}]}"}]}} - -=== get_state === -{"jsonrpc":"2.0","id":1,"result":{"meta":{"status":"Vellykket.","next_action":"Se over data og fortsett.","choices":[{"id":"continue","label":"Fortsett","style":"primary"}]},"content":[{"type":"text","text":"{}"}]}} - -=== list_customers === -{"jsonrpc":"2.0","id":1,"result":{"meta":{"status":"Vellykket.","next_action":"Se over data og fortsett.","choices":[{"id":"continue","label":"Fortsett","style":"primary"}]},"content":[{"type":"text","text":"{}"}]}} - -=== list_gce_instances === -{"jsonrpc":"2.0","id":1,"result":{"meta":{"status":"Vellykket.","next_action":"Se over data og fortsett.","choices":[{"id":"continue","label":"Fortsett","style":"primary"}]},"content":[{"type":"text","text":"{\"instances\": []}"}]}} - -=== list_builds === -{"jsonrpc":"2.0","id":1,"result":{"meta":{"status":"Vellykket.","next_action":"Se over data og fortsett.","choices":[{"id":"continue","label":"Fortsett","style":"primary"}]},"content":[{"type":"text","text":"{\"builds\": [{\"createTime\": \"2026-07-21T10:54:55.570121371Z\", \"finishTime\": \"2026-07-21T10:59:58.765272Z\", \"id\": \"65330be7-0a51-408d-b9f6-b995545621e8\", \"logUrl\": \"https://console.cloud.google.com/cloud-build/builds/65330be7-0a51-408d-b9f6-b995545621e8?project=357036551735\", \"name\": \"projects/357036551735/locations/global/builds/65330be7-0a51-408d-b9f6-b995545621e8\", \"options\": {\"logging\": \"CLOUD_LOGGING_ONLY\", \"pool\": {}, \"substitutionOption\": \"ALLOW_LOOSE\"}, \"projectId\": \"propane-will-491900-m5\", \"queueTtl\": \"3600s\", \"results\": {\"buildStepImages\": [\"sha256:77418e6e7c7f434c4a98eaff04ef16840cf03649c881c03948e3e213923e3136\", \"sha256:6c9b879570fe1c63a78af0b575ca5ac52f6c2c7e25f76f91ae1f2d6cb2a872ee\", \"sha256:6c9b879570fe1c63a78af0b575ca5ac52f6c2c7e25f76f91ae1f2d6cb2a872ee\", \"sha256:934ae3cc409975d61148e7aa3705ac3676f65b8ff17ad883f731881d77c9b92d\", \"sha256:934ae3cc409975d61148e7aa3705ac3676f65b8ff17ad883f731881d77c9b92d\", \"sha256:934ae3cc409975d61148e7aa3705ac3676f65b8ff17ad883f731881d77c9b92d\"], \"buildStepOutputs\": [\"\", \"\", \"\", \"\", \"\", \"\"], \"buildStepResults\": {\"build\": {}, \"clone\": {}, \"deploy\": {}, \"notify\": {}, \"push\": {}, \"smoke-test\": {}}}, \"serviceAccount\": \"projects/propane-will-491900-m5/serviceAccounts/357036551735-compute@developer.gserviceaccount.com\", \"source\": {\"storageSource\": {\"bucket\": \"propane-will-491900-m5_cloudbuild\", \"generation\": \"1784631295341808\", \"object\": \"source/1784631294.700767-4db4b41dba8c4fccaeefff15d57d5dce.tgz\"}}, \"sourceProvenance\": {\"fileHashes\": {\"gs://propane-will-491900-m5_cloudbuild/source/1784631294.700767-4db4b41dba8c4fccaeefff15d57d5dce.tgz#1784631295341808\": {\"fileHash\": [{\"type\": \"MD5\", \"value\": \"SdDPxmVk8bP3Tngx2r0kNQ==\"}, {\"type\": \"SHA256\", \"value\": \"JxZuy6XoKCU80QXBWFLhv61b_WH7Qqc_rUJibptQjjM=\"}]}}, \"resolvedStorageSource\": {\"bucket\": \"propane-will-491900-m5_cloudbuild\", \"generation\": \"1784631295341808\", \"object\": \"source/1784631294.700767-4db4b41dba8c4fccaeefff15d57d5dce.tgz\"}}, \"startTime\": \"2026-07-21T10:54:56.290522993Z\", \"status\": \"SUCCESS\", \"steps\": [{\"args\": [\"-c\", \"git clone --depth=1 --branch=main \\\\\\n http://34.170.51.84:3000/chris/osvauco.git /workspace/src\\ncp -r /workspace/src/. /workspace/\\n\"], \"entrypoint\": \"sh\", \"id\": \"clone\", \"name\": \"alpine/git\", \"pullTiming\": {\"endTime\": \"2026-07-21T10:55:08.009600049Z\", \"startTime\": \"2026-07-21T10:55:01.631590450Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T10:55:09.352196741Z\", \"startTime\": \"2026-07-21T10:55:01.631590450Z\"}}, {\"args\": [\"build\", \"--cache-from\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:latest\", \"-t\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:65330be7-0a51-408d-b9f6-b995545621e8\", \"-t\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:latest\", \"opax-mcp\", \"-f\", \"opax-mcp/Dockerfile\"], \"id\": \"build\", \"name\": \"gcr.io/cloud-builders/docker\", \"pullTiming\": {\"endTime\": \"2026-07-21T10:55:09.358902308Z\", \"startTime\": \"2026-07-21T10:55:09.352295590Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T10:57:14.921981485Z\", \"startTime\": \"2026-07-21T10:55:09.352295590Z\"}}, {\"args\": [\"push\", \"--all-tags\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp\"], \"id\": \"push\", \"name\": \"gcr.io/cloud-builders/docker\", \"pullTiming\": {\"endTime\": \"2026-07-21T10:57:14.923670977Z\", \"startTime\": \"2026-07-21T10:57:14.922111131Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T10:58:01.217337502Z\", \"startTime\": \"2026-07-21T10:57:14.922111131Z\"}, \"waitFor\": [\"build\"]}, {\"args\": [\"run\", \"deploy\", \"opax-mcp\", \"--image=us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:65330be7-0a51-408d-b9f6-b995545621e8\", \"--region=us-central1\", \"--project=propane-will-491900-m5\", \"--service-account=jason-vauger@propane-will-491900-m5.iam.gserviceaccount.com\", \"--set-env-vars=GOOGLE_CLOUD_PROJECT=propane-will-491900-m5,OPAX_BASE_URL=https://opax.vauco.no,OSVAUCO_AGENT_URL=https://osvauco-agent-357036551735.us-central1.run.app\", \"--update-secrets=MCP_SECRET=mcp-server-key:latest,INTERNAL_API_KEY=INTERNAL_API_KEY:latest\", \"--allow-unauthenticated\", \"--port=8080\", \"--memory=512Mi\", \"--cpu=1\", \"--min-instances=0\", \"--max-instances=2\", \"--quiet\"], \"entrypoint\": \"gcloud\", \"id\": \"deploy\", \"name\": \"gcr.io/google.com/cloudsdktool/cloud-sdk\", \"pullTiming\": {\"endTime\": \"2026-07-21T10:59:11.652432801Z\", \"startTime\": \"2026-07-21T10:58:01.217466398Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T10:59:54.329174364Z\", \"startTime\": \"2026-07-21T10:58:01.217466398Z\"}, \"waitFor\": [\"push\"]}, {\"args\": [\"-c\", \"echo \\\"=== MCP Smoke Test ===\\\"\\nSVC_URL=\\\"https://opax-mcp-357036551735.us-central1.run.app\\\"\\necho \\\"Service URL: $SVC_URL\\\"\\nTOKEN=$(curl -s -H \\\"Metadata-Flavor: Google\\\" \\\\\\n \\\"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=$SVC_URL&format=full\\\")\\nSTATUS=$(curl -s -o /dev/null -w \\\"%{http_code}\\\" \\\\\\n -H \\\"Authorization: Bearer $TOKEN\\\" \\\"$SVC_URL/health\\\" 2>/dev/null || echo \\\"000\\\")\\necho \\\"Health status: $STATUS\\\"\\n[ \\\"$STATUS\\\" = \\\"200\\\" ] && echo \\\"OK\\\" || echo \\\"WARN: health check returnerte $STATUS\\\"\\necho \\\"=== Smoke Test ferdig ===\\\"\\n\"], \"entrypoint\": \"bash\", \"id\": \"smoke-test\", \"name\": \"gcr.io/google.com/cloudsdktool/cloud-sdk\", \"pullTiming\": {\"endTime\": \"2026-07-21T10:59:54.330813470Z\", \"startTime\": \"2026-07-21T10:59:54.329292983Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T10:59:55.428771154Z\", \"startTime\": \"2026-07-21T10:59:54.329292983Z\"}, \"waitFor\": [\"deploy\"]}, {\"args\": [\"-c\", \"WH_URL=$(gcloud secrets versions access latest \\\\\\n --secret=webhook-url --project=propane-will-491900-m5 2>/dev/null || echo \\\"\\\")\\nif [[ -n \\\"$WH_URL\\\" ]]; then\\n MSG=\\\"✅ opax-mcp deploy fullført\\\\nCommit: \\\\nService: opax-mcp (us-central1)\\\\nhttps://opax-mcp-357036551735.us-central1.run.app\\\"\\n curl -s -X POST \\\"$WH_URL\\\" \\\\\\n -H 'Content-Type: application/json' \\\\\\n -d \\\"{\\\\\\\"text\\\\\\\": \\\\\\\"$MSG\\\\\\\"}\\\" || true\\n echo \\\"✓ Varsel sendt\\\"\\nelse\\n echo \\\"⚠️ Ingen webhook-url — hopper over varsling\\\"\\nfi\\n\"], \"entrypoint\": \"bash\", \"id\": \"notify\", \"name\": \"gcr.io/google.com/cloudsdktool/cloud-sdk\", \"pullTiming\": {\"endTime\": \"2026-07-21T10:59:55.430395377Z\", \"startTime\": \"2026-07-21T10:59:55.428896750Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T10:59:57.937435214Z\", \"startTime\": \"2026-07-21T10:59:55.428896750Z\"}, \"waitFor\": [\"smoke-test\"]}], \"substitutions\": {\"_ARTIFACT_REPO\": \"osvauco-repo\", \"_MCP_SA\": \"jason-vauger@propane-will-491900-m5.iam.gserviceaccount.com\", \"_REGION\": \"us-central1\"}, \"timeout\": \"3600s\", \"timing\": {\"BUILD\": {\"endTime\": \"2026-07-21T10:59:57.937538059Z\", \"startTime\": \"2026-07-21T10:55:01.003109105Z\"}, \"FETCHSOURCE\": {\"endTime\": \"2026-07-21T10:55:01.003015091Z\", \"startTime\": \"2026-07-21T10:54:57.980489515Z\"}, \"STORAGE_SOURCE\": {\"endTime\": \"2026-07-21T10:55:01.003015091Z\", \"startTime\": \"2026-07-21T10:54:57.980489515Z\"}}}, {\"createTime\": \"2026-07-21T10:35:24.806771139Z\", \"finishTime\": \"2026-07-21T10:41:23.757987Z\", \"id\": \"8bf93c75-ce92-4320-ac73-93fc6da57318\", \"logUrl\": \"https://console.cloud.google.com/cloud-build/builds/8bf93c75-ce92-4320-ac73-93fc6da57318?project=357036551735\", \"name\": \"projects/357036551735/locations/global/builds/8bf93c75-ce92-4320-ac73-93fc6da57318\", \"options\": {\"logging\": \"CLOUD_LOGGING_ONLY\", \"pool\": {}, \"substitutionOption\": \"ALLOW_LOOSE\"}, \"projectId\": \"propane-will-491900-m5\", \"queueTtl\": \"3600s\", \"results\": {\"buildStepImages\": [\"sha256:77418e6e7c7f434c4a98eaff04ef16840cf03649c881c03948e3e213923e3136\", \"sha256:6c9b879570fe1c63a78af0b575ca5ac52f6c2c7e25f76f91ae1f2d6cb2a872ee\", \"sha256:6c9b879570fe1c63a78af0b575ca5ac52f6c2c7e25f76f91ae1f2d6cb2a872ee\", \"sha256:934ae3cc409975d61148e7aa3705ac3676f65b8ff17ad883f731881d77c9b92d\", \"sha256:934ae3cc409975d61148e7aa3705ac3676f65b8ff17ad883f731881d77c9b92d\", \"sha256:934ae3cc409975d61148e7aa3705ac3676f65b8ff17ad883f731881d77c9b92d\"], \"buildStepOutputs\": [\"\", \"\", \"\", \"\", \"\", \"\"], \"buildStepResults\": {\"build\": {}, \"clone\": {}, \"deploy\": {}, \"notify\": {}, \"push\": {}, \"smoke-test\": {}}}, \"serviceAccount\": \"projects/propane-will-491900-m5/serviceAccounts/357036551735-compute@developer.gserviceaccount.com\", \"source\": {\"storageSource\": {\"bucket\": \"propane-will-491900-m5_cloudbuild\", \"generation\": \"1784630124588074\", \"object\": \"source/1784630124.000642-3aedf7a4e6ab4af6a391119b1230d923.tgz\"}}, \"sourceProvenance\": {\"fileHashes\": {\"gs://propane-will-491900-m5_cloudbuild/source/1784630124.000642-3aedf7a4e6ab4af6a391119b1230d923.tgz#1784630124588074\": {\"fileHash\": [{\"type\": \"MD5\", \"value\": \"Qa2OOA_wmGvNBoVYrkJ_BQ==\"}, {\"type\": \"SHA256\", \"value\": \"0MEWPnIWrJyYDWFsHptcSF8zWwl138HQDIqrxVj4_xU=\"}]}}, \"resolvedStorageSource\": {\"bucket\": \"propane-will-491900-m5_cloudbuild\", \"generation\": \"1784630124588074\", \"object\": \"source/1784630124.000642-3aedf7a4e6ab4af6a391119b1230d923.tgz\"}}, \"startTime\": \"2026-07-21T10:35:25.618815588Z\", \"status\": \"SUCCESS\", \"steps\": [{\"args\": [\"-c\", \"git clone --depth=1 --branch=main \\\\\\n http://34.170.51.84:3000/chris/osvauco.git /workspace/src\\ncp -r /workspace/src/. /workspace/\\n\"], \"entrypoint\": \"sh\", \"id\": \"clone\", \"name\": \"alpine/git\", \"pullTiming\": {\"endTime\": \"2026-07-21T10:35:35.811288872Z\", \"startTime\": \"2026-07-21T10:35:30.553166952Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T10:35:37.038046905Z\", \"startTime\": \"2026-07-21T10:35:30.553166952Z\"}}, {\"args\": [\"build\", \"--cache-from\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:latest\", \"-t\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:8bf93c75-ce92-4320-ac73-93fc6da57318\", \"-t\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:latest\", \"opax-mcp\", \"-f\", \"opax-mcp/Dockerfile\"], \"id\": \"build\", \"name\": \"gcr.io/cloud-builders/docker\", \"pullTiming\": {\"endTime\": \"2026-07-21T10:35:37.045280896Z\", \"startTime\": \"2026-07-21T10:35:37.038136164Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T10:37:39.884968082Z\", \"startTime\": \"2026-07-21T10:35:37.038136164Z\"}}, {\"args\": [\"push\", \"--all-tags\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp\"], \"id\": \"push\", \"name\": \"gcr.io/cloud-builders/docker\", \"pullTiming\": {\"endTime\": \"2026-07-21T10:37:39.886764862Z\", \"startTime\": \"2026-07-21T10:37:39.885067786Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T10:38:26.942295488Z\", \"startTime\": \"2026-07-21T10:37:39.885067786Z\"}, \"waitFor\": [\"build\"]}, {\"args\": [\"run\", \"deploy\", \"opax-mcp\", \"--image=us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:8bf93c75-ce92-4320-ac73-93fc6da57318\", \"--region=us-central1\", \"--project=propane-will-491900-m5\", \"--service-account=jason-vauger@propane-will-491900-m5.iam.gserviceaccount.com\", \"--set-env-vars=GOOGLE_CLOUD_PROJECT=propane-will-491900-m5,OPAX_BASE_URL=https://opax.vauco.no,OSVAUCO_AGENT_URL=https://osvauco-agent-357036551735.us-central1.run.app\", \"--update-secrets=MCP_SECRET=mcp-server-key:latest,INTERNAL_API_KEY=INTERNAL_API_KEY:latest\", \"--allow-unauthenticated\", \"--port=8080\", \"--memory=512Mi\", \"--cpu=1\", \"--min-instances=0\", \"--max-instances=2\", \"--quiet\"], \"entrypoint\": \"gcloud\", \"id\": \"deploy\", \"name\": \"gcr.io/google.com/cloudsdktool/cloud-sdk\", \"pullTiming\": {\"endTime\": \"2026-07-21T10:39:33.188998705Z\", \"startTime\": \"2026-07-21T10:38:26.942410435Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T10:41:18.055147904Z\", \"startTime\": \"2026-07-21T10:38:26.942410435Z\"}, \"waitFor\": [\"push\"]}, {\"args\": [\"-c\", \"echo \\\"=== MCP Smoke Test ===\\\"\\nSVC_URL=\\\"https://opax-mcp-zjbqp3prqq-uc.a.run.app\\\"\\necho \\\"Service URL: $SVC_URL\\\"\\nTOKEN=$(curl -s -H \\\"Metadata-Flavor: Google\\\" \\\\\\n \\\"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=$SVC_URL&format=full\\\")\\nSTATUS=$(curl -s -o /dev/null -w \\\"%{http_code}\\\" \\\\\\n -H \\\"Authorization: Bearer $TOKEN\\\" \\\"$SVC_URL/health\\\" 2>/dev/null || echo \\\"000\\\")\\necho \\\"Health status: $STATUS\\\"\\n[ \\\"$STATUS\\\" = \\\"200\\\" ] && echo \\\"OK\\\" || echo \\\"WARN: health check returnerte $STATUS\\\"\\necho \\\"=== Smoke Test ferdig ===\\\"\\n\"], \"entrypoint\": \"bash\", \"id\": \"smoke-test\", \"name\": \"gcr.io/google.com/cloudsdktool/cloud-sdk\", \"pullTiming\": {\"endTime\": \"2026-07-21T10:41:18.059977058Z\", \"startTime\": \"2026-07-21T10:41:18.055436019Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T10:41:20.755606975Z\", \"startTime\": \"2026-07-21T10:41:18.055436019Z\"}, \"waitFor\": [\"deploy\"]}, {\"args\": [\"-c\", \"WH_URL=$(gcloud secrets versions access latest \\\\\\n --secret=webhook-url --project=propane-will-491900-m5 2>/dev/null || echo \\\"\\\")\\nif [[ -n \\\"$WH_URL\\\" ]]; then\\n MSG=\\\"✅ opax-mcp deploy fullført\\\\nCommit: \\\\nService: opax-mcp (us-central1)\\\\nhttps://opax-mcp-zjbqp3prqq-uc.a.run.app\\\"\\n curl -s -X POST \\\"$WH_URL\\\" \\\\\\n -H 'Content-Type: application/json' \\\\\\n -d \\\"{\\\\\\\"text\\\\\\\": \\\\\\\"$MSG\\\\\\\"}\\\" || true\\n echo \\\"✓ Varsel sendt\\\"\\nelse\\n echo \\\"⚠️ Ingen webhook-url — hopper over varsling\\\"\\nfi\\n\"], \"entrypoint\": \"bash\", \"id\": \"notify\", \"name\": \"gcr.io/google.com/cloudsdktool/cloud-sdk\", \"pullTiming\": {\"endTime\": \"2026-07-21T10:41:20.757352738Z\", \"startTime\": \"2026-07-21T10:41:20.755721392Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T10:41:23.461490189Z\", \"startTime\": \"2026-07-21T10:41:20.755721392Z\"}, \"waitFor\": [\"smoke-test\"]}], \"substitutions\": {\"_ARTIFACT_REPO\": \"osvauco-repo\", \"_MCP_SA\": \"jason-vauger@propane-will-491900-m5.iam.gserviceaccount.com\", \"_REGION\": \"us-central1\"}, \"timeout\": \"3600s\", \"timing\": {\"BUILD\": {\"endTime\": \"2026-07-21T10:41:23.461582336Z\", \"startTime\": \"2026-07-21T10:35:29.951434050Z\"}, \"FETCHSOURCE\": {\"endTime\": \"2026-07-21T10:35:29.951307136Z\", \"startTime\": \"2026-07-21T10:35:27.011176309Z\"}, \"STORAGE_SOURCE\": {\"endTime\": \"2026-07-21T10:35:29.951307136Z\", \"startTime\": \"2026-07-21T10:35:27.011176309Z\"}}}, {\"createTime\": \"2026-07-21T10:25:07.444060058Z\", \"finishTime\": \"2026-07-21T10:29:58.170616Z\", \"id\": \"9a812dfc-b693-4635-8d57-45b7151deb4d\", \"logUrl\": \"https://console.cloud.google.com/cloud-build/builds/9a812dfc-b693-4635-8d57-45b7151deb4d?project=357036551735\", \"name\": \"projects/357036551735/locations/global/builds/9a812dfc-b693-4635-8d57-45b7151deb4d\", \"options\": {\"logging\": \"CLOUD_LOGGING_ONLY\", \"pool\": {}, \"substitutionOption\": \"ALLOW_LOOSE\"}, \"projectId\": \"propane-will-491900-m5\", \"queueTtl\": \"3600s\", \"results\": {\"buildStepImages\": [\"sha256:77418e6e7c7f434c4a98eaff04ef16840cf03649c881c03948e3e213923e3136\", \"sha256:6c9b879570fe1c63a78af0b575ca5ac52f6c2c7e25f76f91ae1f2d6cb2a872ee\", \"sha256:6c9b879570fe1c63a78af0b575ca5ac52f6c2c7e25f76f91ae1f2d6cb2a872ee\", \"sha256:934ae3cc409975d61148e7aa3705ac3676f65b8ff17ad883f731881d77c9b92d\", \"sha256:934ae3cc409975d61148e7aa3705ac3676f65b8ff17ad883f731881d77c9b92d\", \"sha256:934ae3cc409975d61148e7aa3705ac3676f65b8ff17ad883f731881d77c9b92d\"], \"buildStepOutputs\": [\"\", \"\", \"\", \"\", \"\", \"\"], \"buildStepResults\": {\"build\": {}, \"clone\": {}, \"deploy\": {}, \"notify\": {}, \"push\": {}, \"smoke-test\": {}}}, \"serviceAccount\": \"projects/propane-will-491900-m5/serviceAccounts/357036551735-compute@developer.gserviceaccount.com\", \"source\": {\"storageSource\": {\"bucket\": \"propane-will-491900-m5_cloudbuild\", \"generation\": \"1784629507205466\", \"object\": \"source/1784629506.609256-7d1261c7249248eea1ac080e899bdfaf.tgz\"}}, \"sourceProvenance\": {\"fileHashes\": {\"gs://propane-will-491900-m5_cloudbuild/source/1784629506.609256-7d1261c7249248eea1ac080e899bdfaf.tgz#1784629507205466\": {\"fileHash\": [{\"type\": \"MD5\", \"value\": \"Vgvf2t729JbzOeR_JoBj6g==\"}, {\"type\": \"SHA256\", \"value\": \"rNKKZyh722ykUl1I_Oh64RifhpSuR7wOtiZKAvsObnE=\"}]}}, \"resolvedStorageSource\": {\"bucket\": \"propane-will-491900-m5_cloudbuild\", \"generation\": \"1784629507205466\", \"object\": \"source/1784629506.609256-7d1261c7249248eea1ac080e899bdfaf.tgz\"}}, \"startTime\": \"2026-07-21T10:25:08.356110480Z\", \"status\": \"SUCCESS\", \"steps\": [{\"args\": [\"-c\", \"git clone --depth=1 --branch=main \\\\\\n http://34.170.51.84:3000/chris/osvauco.git /workspace/src\\ncp -r /workspace/src/. /workspace/\\n\"], \"entrypoint\": \"sh\", \"id\": \"clone\", \"name\": \"alpine/git\", \"pullTiming\": {\"endTime\": \"2026-07-21T10:25:19.065100327Z\", \"startTime\": \"2026-07-21T10:25:13.606629662Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T10:25:20.273540294Z\", \"startTime\": \"2026-07-21T10:25:13.606629662Z\"}}, {\"args\": [\"build\", \"--cache-from\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:latest\", \"-t\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:9a812dfc-b693-4635-8d57-45b7151deb4d\", \"-t\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:latest\", \"opax-mcp\", \"-f\", \"opax-mcp/Dockerfile\"], \"id\": \"build\", \"name\": \"gcr.io/cloud-builders/docker\", \"pullTiming\": {\"endTime\": \"2026-07-21T10:25:20.278877618Z\", \"startTime\": \"2026-07-21T10:25:20.273650844Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T10:27:23.509473522Z\", \"startTime\": \"2026-07-21T10:25:20.273650844Z\"}}, {\"args\": [\"push\", \"--all-tags\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp\"], \"id\": \"push\", \"name\": \"gcr.io/cloud-builders/docker\", \"pullTiming\": {\"endTime\": \"2026-07-21T10:27:23.511118546Z\", \"startTime\": \"2026-07-21T10:27:23.509556234Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T10:28:08.950140027Z\", \"startTime\": \"2026-07-21T10:27:23.509556234Z\"}, \"waitFor\": [\"build\"]}, {\"args\": [\"run\", \"deploy\", \"opax-mcp\", \"--image=us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:9a812dfc-b693-4635-8d57-45b7151deb4d\", \"--region=us-central1\", \"--project=propane-will-491900-m5\", \"--service-account=jason-vauger@propane-will-491900-m5.iam.gserviceaccount.com\", \"--set-env-vars=GOOGLE_CLOUD_PROJECT=propane-will-491900-m5,OPAX_BASE_URL=https://opax.vauco.no,OSVAUCO_AGENT_URL=https://osvauco-agent-zjbqp3prqq-uc.a.run.app\", \"--update-secrets=MCP_SECRET=mcp-server-key:latest,INTERNAL_API_KEY=INTERNAL_API_KEY:latest\", \"--allow-unauthenticated\", \"--port=8080\", \"--memory=512Mi\", \"--cpu=1\", \"--min-instances=0\", \"--max-instances=2\", \"--quiet\"], \"entrypoint\": \"gcloud\", \"id\": \"deploy\", \"name\": \"gcr.io/google.com/cloudsdktool/cloud-sdk\", \"pullTiming\": {\"endTime\": \"2026-07-21T10:29:18.933535292Z\", \"startTime\": \"2026-07-21T10:28:08.950233675Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T10:29:54.362231300Z\", \"startTime\": \"2026-07-21T10:28:08.950233675Z\"}, \"waitFor\": [\"push\"]}, {\"args\": [\"-c\", \"echo \\\"=== MCP Smoke Test ===\\\"\\nSVC_URL=\\\"https://opax-mcp-zjbqp3prqq-uc.a.run.app\\\"\\necho \\\"Service URL: $SVC_URL\\\"\\nTOKEN=$(curl -s -H \\\"Metadata-Flavor: Google\\\" \\\\\\n \\\"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=$SVC_URL&format=full\\\")\\nSTATUS=$(curl -s -o /dev/null -w \\\"%{http_code}\\\" \\\\\\n -H \\\"Authorization: Bearer $TOKEN\\\" \\\"$SVC_URL/health\\\" 2>/dev/null || echo \\\"000\\\")\\necho \\\"Health status: $STATUS\\\"\\n[ \\\"$STATUS\\\" = \\\"200\\\" ] && echo \\\"OK\\\" || echo \\\"WARN: health check returnerte $STATUS\\\"\\necho \\\"=== Smoke Test ferdig ===\\\"\\n\"], \"entrypoint\": \"bash\", \"id\": \"smoke-test\", \"name\": \"gcr.io/google.com/cloudsdktool/cloud-sdk\", \"pullTiming\": {\"endTime\": \"2026-07-21T10:29:54.364381885Z\", \"startTime\": \"2026-07-21T10:29:54.362616699Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T10:29:55.312195505Z\", \"startTime\": \"2026-07-21T10:29:54.362616699Z\"}, \"waitFor\": [\"deploy\"]}, {\"args\": [\"-c\", \"WH_URL=$(gcloud secrets versions access latest \\\\\\n --secret=webhook-url --project=propane-will-491900-m5 2>/dev/null || echo \\\"\\\")\\nif [[ -n \\\"$WH_URL\\\" ]]; then\\n MSG=\\\"✅ opax-mcp deploy fullført\\\\nCommit: \\\\nService: opax-mcp (us-central1)\\\\nhttps://opax-mcp-zjbqp3prqq-uc.a.run.app\\\"\\n curl -s -X POST \\\"$WH_URL\\\" \\\\\\n -H 'Content-Type: application/json' \\\\\\n -d \\\"{\\\\\\\"text\\\\\\\": \\\\\\\"$MSG\\\\\\\"}\\\" || true\\n echo \\\"✓ Varsel sendt\\\"\\nelse\\n echo \\\"⚠️ Ingen webhook-url — hopper over varsling\\\"\\nfi\\n\"], \"entrypoint\": \"bash\", \"id\": \"notify\", \"name\": \"gcr.io/google.com/cloudsdktool/cloud-sdk\", \"pullTiming\": {\"endTime\": \"2026-07-21T10:29:55.314044669Z\", \"startTime\": \"2026-07-21T10:29:55.312294507Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T10:29:57.735815093Z\", \"startTime\": \"2026-07-21T10:29:55.312294507Z\"}, \"waitFor\": [\"smoke-test\"]}], \"substitutions\": {\"_ARTIFACT_REPO\": \"osvauco-repo\", \"_MCP_SA\": \"jason-vauger@propane-will-491900-m5.iam.gserviceaccount.com\", \"_REGION\": \"us-central1\"}, \"timeout\": \"3600s\", \"timing\": {\"BUILD\": {\"endTime\": \"2026-07-21T10:29:57.735945189Z\", \"startTime\": \"2026-07-21T10:25:12.962784107Z\"}, \"FETCHSOURCE\": {\"endTime\": \"2026-07-21T10:25:12.962381892Z\", \"startTime\": \"2026-07-21T10:25:09.911479145Z\"}, \"STORAGE_SOURCE\": {\"endTime\": \"2026-07-21T10:25:12.962381892Z\", \"startTime\": \"2026-07-21T10:25:09.911479145Z\"}}}, {\"createTime\": \"2026-07-21T10:07:55.277027971Z\", \"finishTime\": \"2026-07-21T10:13:08.334881Z\", \"id\": \"16b46e3d-ce5d-47dd-90b0-d50d148440d0\", \"logUrl\": \"https://console.cloud.google.com/cloud-build/builds/16b46e3d-ce5d-47dd-90b0-d50d148440d0?project=357036551735\", \"name\": \"projects/357036551735/locations/global/builds/16b46e3d-ce5d-47dd-90b0-d50d148440d0\", \"options\": {\"logging\": \"CLOUD_LOGGING_ONLY\", \"pool\": {}, \"substitutionOption\": \"ALLOW_LOOSE\"}, \"projectId\": \"propane-will-491900-m5\", \"queueTtl\": \"3600s\", \"results\": {\"buildStepImages\": [\"sha256:934ae3cc409975d61148e7aa3705ac3676f65b8ff17ad883f731881d77c9b92d\", \"sha256:6c9b879570fe1c63a78af0b575ca5ac52f6c2c7e25f76f91ae1f2d6cb2a872ee\", \"sha256:6c9b879570fe1c63a78af0b575ca5ac52f6c2c7e25f76f91ae1f2d6cb2a872ee\", \"sha256:934ae3cc409975d61148e7aa3705ac3676f65b8ff17ad883f731881d77c9b92d\", \"sha256:934ae3cc409975d61148e7aa3705ac3676f65b8ff17ad883f731881d77c9b92d\", \"sha256:934ae3cc409975d61148e7aa3705ac3676f65b8ff17ad883f731881d77c9b92d\"], \"buildStepOutputs\": [\"\", \"\", \"\", \"\", \"\", \"\"], \"buildStepResults\": {\"build\": {}, \"cost-check\": {}, \"deploy\": {}, \"notify\": {}, \"push\": {}, \"smoke-test\": {}}}, \"serviceAccount\": \"projects/propane-will-491900-m5/serviceAccounts/357036551735-compute@developer.gserviceaccount.com\", \"source\": {\"storageSource\": {\"bucket\": \"propane-will-491900-m5_cloudbuild\", \"generation\": \"1784628475048247\", \"object\": \"source/1784628474.457882-845dc4ca002b44619f805874d2b13cf4.tgz\"}}, \"sourceProvenance\": {\"fileHashes\": {\"gs://propane-will-491900-m5_cloudbuild/source/1784628474.457882-845dc4ca002b44619f805874d2b13cf4.tgz#1784628475048247\": {\"fileHash\": [{\"type\": \"MD5\", \"value\": \"92ccWqZVdL5stHlAcujs9w==\"}, {\"type\": \"SHA256\", \"value\": \"rHZozDjlfeNgNAfgG3IdInCw2zPtGmUEzBKbhOrnFeg=\"}]}}, \"resolvedStorageSource\": {\"bucket\": \"propane-will-491900-m5_cloudbuild\", \"generation\": \"1784628475048247\", \"object\": \"source/1784628474.457882-845dc4ca002b44619f805874d2b13cf4.tgz\"}}, \"startTime\": \"2026-07-21T10:07:56.049627456Z\", \"status\": \"SUCCESS\", \"steps\": [{\"args\": [\"-c\", \"echo \\\"=== Cost Check ===\\\"\\nbash infrastructure/09-cost-check.sh || true\\necho \\\"=== Cost Check ferdig ===\\\"\\n\"], \"entrypoint\": \"bash\", \"id\": \"cost-check\", \"name\": \"gcr.io/google.com/cloudsdktool/cloud-sdk\", \"pullTiming\": {\"endTime\": \"2026-07-21T10:09:33.196544078Z\", \"startTime\": \"2026-07-21T10:08:03.129699615Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T10:09:34.046023763Z\", \"startTime\": \"2026-07-21T10:08:03.129699615Z\"}}, {\"args\": [\"build\", \"--cache-from\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/osvauco-agent:latest\", \"-t\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/osvauco-agent:16b46e3d-ce5d-47dd-90b0-d50d148440d0\", \"-t\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/osvauco-agent:latest\", \".\", \"-f\", \"agents/core-logic/Dockerfile\"], \"id\": \"build\", \"name\": \"gcr.io/cloud-builders/docker\", \"pullTiming\": {\"endTime\": \"2026-07-21T10:09:34.055206448Z\", \"startTime\": \"2026-07-21T10:09:34.046140810Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T10:11:20.895385462Z\", \"startTime\": \"2026-07-21T10:09:34.046140810Z\"}}, {\"args\": [\"push\", \"--all-tags\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/osvauco-agent\"], \"id\": \"push\", \"name\": \"gcr.io/cloud-builders/docker\", \"pullTiming\": {\"endTime\": \"2026-07-21T10:11:20.897525686Z\", \"startTime\": \"2026-07-21T10:11:20.895501933Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T10:12:01.891174554Z\", \"startTime\": \"2026-07-21T10:11:20.895501933Z\"}, \"waitFor\": [\"build\"]}, {\"args\": [\"run\", \"deploy\", \"osvauco-agent\", \"--image=us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/osvauco-agent:16b46e3d-ce5d-47dd-90b0-d50d148440d0\", \"--region=us-central1\", \"--project=propane-will-491900-m5\", \"--service-account=osvauco-agent-sa@propane-will-491900-m5.iam.gserviceaccount.com\", \"--set-env-vars=GOOGLE_CLOUD_PROJECT=propane-will-491900-m5,GOOGLE_CLOUD_LOCATION=global,GOOGLE_GENAI_USE_VERTEXAI=True,BILLING_TABLE=billing_export.gcp_billing_export_v1_0171F6_057E6B_A260BA,RAG_CORPUS=projects/propane-will-491900-m5/locations/europe-west4/ragCorpora/6917529027641081856,RAG_LOCATION=europe-west4\", \"--set-secrets=GOOGLE_CLIENT_ID=GOOGLE_CLIENT_ID:1,SESSION_SECRET=SESSION_SECRET:1,GOOGLE_CLIENT_SECRET=GOOGLE_CLIENT_SECRET:1,MCP_SECRET=mcp-server-key:latest,INTERNAL_API_KEY=INTERNAL_API_KEY:latest\", \"--no-allow-unauthenticated\", \"--port=8080\", \"--memory=1Gi\", \"--cpu=1\", \"--min-instances=0\", \"--max-instances=3\", \"--quiet\"], \"entrypoint\": \"gcloud\", \"id\": \"deploy\", \"name\": \"gcr.io/google.com/cloudsdktool/cloud-sdk\", \"pullTiming\": {\"endTime\": \"2026-07-21T10:12:01.893512985Z\", \"startTime\": \"2026-07-21T10:12:01.891306902Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T10:13:03.830028439Z\", \"startTime\": \"2026-07-21T10:12:01.891306902Z\"}, \"waitFor\": [\"push\"]}, {\"args\": [\"-c\", \"echo \\\"=== Smoke Test ===\\\"\\nBASE_URL=\\\"https://osvauco-agent-zjbqp3prqq-uc.a.run.app\\\"\\necho \\\"BASE_URL: $BASE_URL\\\"\\n\\nTOKEN=$(curl -s -H \\\"Metadata-Flavor: Google\\\" \\\"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=$BASE_URL&format=full\\\")\\necho \\\"TOKEN_EMPTY: $([ -z \\\"$TOKEN\\\" ] && echo YES || echo NO)\\\"\\n\\ncurl -s -H \\\"Authorization: Bearer $TOKEN\\\" \\\"$BASE_URL/health\\\" && echo \\\"OK\\\" || echo \\\"WARN: health check feilet\\\"\\necho \\\"=== Smoke Test ferdig ===\\\"\\n\"], \"entrypoint\": \"bash\", \"id\": \"smoke-test\", \"name\": \"gcr.io/google.com/cloudsdktool/cloud-sdk\", \"pullTiming\": {\"endTime\": \"2026-07-21T10:13:03.831981543Z\", \"startTime\": \"2026-07-21T10:13:03.830177490Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T10:13:05.017010415Z\", \"startTime\": \"2026-07-21T10:13:03.830177490Z\"}, \"waitFor\": [\"deploy\"]}, {\"args\": [\"-c\", \"WH_URL=$(gcloud secrets versions access latest \\\\\\n --secret=webhook-url \\\\\\n --project=propane-will-491900-m5 2>/dev/null || echo \\\"\\\")\\nif [[ -n \\\"$WH_URL\\\" ]]; then\\n MSG=\\\"✅ OSVauco deploy fullført\\\\nCommit: \\\\nService: osvauco-agent (us-central1)\\\\nhttps://opax.vauco.no\\\"\\n curl -s -X POST \\\"$WH_URL\\\" \\\\\\n -H 'Content-Type: application/json' \\\\\\n -d \\\"{\\\\\\\"text\\\\\\\": \\\\\\\"$MSG\\\\\\\"}\\\" || true\\n echo \\\"✓ Google Chat-varsling sendt\\\"\\nelse\\n echo \\\"⚠️ Ingen webhook-url i Secret Manager — hopper over varsling\\\"\\nfi\\n\"], \"entrypoint\": \"bash\", \"id\": \"notify\", \"name\": \"gcr.io/google.com/cloudsdktool/cloud-sdk\", \"pullTiming\": {\"endTime\": \"2026-07-21T10:13:05.018886743Z\", \"startTime\": \"2026-07-21T10:13:05.017206762Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T10:13:07.773819967Z\", \"startTime\": \"2026-07-21T10:13:05.017206762Z\"}, \"waitFor\": [\"smoke-test\"]}], \"substitutions\": {\"_AGENT_SA\": \"osvauco-agent-sa@propane-will-491900-m5.iam.gserviceaccount.com\", \"_ARTIFACT_REPO\": \"osvauco-repo\", \"_CLOUD_RUN_SERVICE\": \"osvauco-agent\", \"_CLOUD_RUN_URL\": \"osvauco-agent-zjbqp3prqq-uc.a.run.app\", \"_GEMINI_LOCATION\": \"global\", \"_REGION\": \"us-central1\"}, \"timeout\": \"3600s\", \"timing\": {\"BUILD\": {\"endTime\": \"2026-07-21T10:13:07.773990779Z\", \"startTime\": \"2026-07-21T10:08:02.410627733Z\"}, \"FETCHSOURCE\": {\"endTime\": \"2026-07-21T10:08:02.410491739Z\", \"startTime\": \"2026-07-21T10:07:58.912751351Z\"}, \"STORAGE_SOURCE\": {\"endTime\": \"2026-07-21T10:08:02.410491739Z\", \"startTime\": \"2026-07-21T10:07:58.912751351Z\"}}}, {\"createTime\": \"2026-07-21T09:53:35.092989903Z\", \"finishTime\": \"2026-07-21T09:59:44.146993Z\", \"id\": \"aa3573e6-caa4-4c7b-b4cd-2f3712f5c0f7\", \"logUrl\": \"https://console.cloud.google.com/cloud-build/builds/aa3573e6-caa4-4c7b-b4cd-2f3712f5c0f7?project=357036551735\", \"name\": \"projects/357036551735/locations/global/builds/aa3573e6-caa4-4c7b-b4cd-2f3712f5c0f7\", \"options\": {\"logging\": \"CLOUD_LOGGING_ONLY\", \"pool\": {}, \"substitutionOption\": \"ALLOW_LOOSE\"}, \"projectId\": \"propane-will-491900-m5\", \"queueTtl\": \"3600s\", \"results\": {\"buildStepImages\": [\"sha256:77418e6e7c7f434c4a98eaff04ef16840cf03649c881c03948e3e213923e3136\", \"sha256:6c9b879570fe1c63a78af0b575ca5ac52f6c2c7e25f76f91ae1f2d6cb2a872ee\", \"sha256:6c9b879570fe1c63a78af0b575ca5ac52f6c2c7e25f76f91ae1f2d6cb2a872ee\", \"sha256:934ae3cc409975d61148e7aa3705ac3676f65b8ff17ad883f731881d77c9b92d\", \"sha256:934ae3cc409975d61148e7aa3705ac3676f65b8ff17ad883f731881d77c9b92d\", \"sha256:934ae3cc409975d61148e7aa3705ac3676f65b8ff17ad883f731881d77c9b92d\"], \"buildStepOutputs\": [\"\", \"\", \"\", \"\", \"\", \"\"], \"buildStepResults\": {\"build\": {}, \"clone\": {}, \"deploy\": {}, \"notify\": {}, \"push\": {}, \"smoke-test\": {}}}, \"serviceAccount\": \"projects/propane-will-491900-m5/serviceAccounts/357036551735-compute@developer.gserviceaccount.com\", \"source\": {\"storageSource\": {\"bucket\": \"propane-will-491900-m5_cloudbuild\", \"generation\": \"1784627614884676\", \"object\": \"source/1784627614.276774-7173c3df1fda4c11af649e8d0ff062a4.tgz\"}}, \"sourceProvenance\": {\"fileHashes\": {\"gs://propane-will-491900-m5_cloudbuild/source/1784627614.276774-7173c3df1fda4c11af649e8d0ff062a4.tgz#1784627614884676\": {\"fileHash\": [{\"type\": \"MD5\", \"value\": \"HfSQ-13LweUhTicQsfR-gQ==\"}, {\"type\": \"SHA256\", \"value\": \"FlwisLI3sB3bFYt4hv_Cq1XRl62L9cAjZITlDKNqfq4=\"}]}}, \"resolvedStorageSource\": {\"bucket\": \"propane-will-491900-m5_cloudbuild\", \"generation\": \"1784627614884676\", \"object\": \"source/1784627614.276774-7173c3df1fda4c11af649e8d0ff062a4.tgz\"}}, \"startTime\": \"2026-07-21T09:53:35.792502678Z\", \"status\": \"SUCCESS\", \"steps\": [{\"args\": [\"-c\", \"git clone --depth=1 --branch=main \\\\\\n http://34.170.51.84:3000/chris/osvauco.git /workspace/src\\ncp -r /workspace/src/. /workspace/\\n\"], \"entrypoint\": \"sh\", \"id\": \"clone\", \"name\": \"alpine/git\", \"pullTiming\": {\"endTime\": \"2026-07-21T09:53:44.788257441Z\", \"startTime\": \"2026-07-21T09:53:40.845115855Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T09:53:45.826286573Z\", \"startTime\": \"2026-07-21T09:53:40.845115855Z\"}}, {\"args\": [\"build\", \"--cache-from\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:latest\", \"-t\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:aa3573e6-caa4-4c7b-b4cd-2f3712f5c0f7\", \"-t\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:latest\", \"opax-mcp\", \"-f\", \"opax-mcp/Dockerfile\"], \"id\": \"build\", \"name\": \"gcr.io/cloud-builders/docker\", \"pullTiming\": {\"endTime\": \"2026-07-21T09:53:45.830762031Z\", \"startTime\": \"2026-07-21T09:53:45.826402644Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T09:55:48.186988716Z\", \"startTime\": \"2026-07-21T09:53:45.826402644Z\"}}, {\"args\": [\"push\", \"--all-tags\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp\"], \"id\": \"push\", \"name\": \"gcr.io/cloud-builders/docker\", \"pullTiming\": {\"endTime\": \"2026-07-21T09:55:48.192462775Z\", \"startTime\": \"2026-07-21T09:55:48.187102439Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T09:56:31.589848787Z\", \"startTime\": \"2026-07-21T09:55:48.187102439Z\"}, \"waitFor\": [\"build\"]}, {\"args\": [\"run\", \"deploy\", \"opax-mcp\", \"--image=us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:aa3573e6-caa4-4c7b-b4cd-2f3712f5c0f7\", \"--region=us-central1\", \"--project=propane-will-491900-m5\", \"--service-account=jason-vauger@propane-will-491900-m5.iam.gserviceaccount.com\", \"--set-env-vars=GOOGLE_CLOUD_PROJECT=propane-will-491900-m5,OPAX_BASE_URL=https://opax.vauco.no,OSVAUCO_AGENT_URL=https://osvauco-agent-zjbqp3prqq-uc.a.run.app\", \"--update-secrets=MCP_SECRET=mcp-server-key:latest,INTERNAL_API_KEY=INTERNAL_API_KEY:latest\", \"--allow-unauthenticated\", \"--port=8080\", \"--memory=512Mi\", \"--cpu=1\", \"--min-instances=0\", \"--max-instances=2\", \"--quiet\"], \"entrypoint\": \"gcloud\", \"id\": \"deploy\", \"name\": \"gcr.io/google.com/cloudsdktool/cloud-sdk\", \"pullTiming\": {\"endTime\": \"2026-07-21T09:57:40.569333293Z\", \"startTime\": \"2026-07-21T09:56:31.589939972Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T09:59:40.267818844Z\", \"startTime\": \"2026-07-21T09:56:31.589939972Z\"}, \"waitFor\": [\"push\"]}, {\"args\": [\"-c\", \"echo \\\"=== MCP Smoke Test ===\\\"\\nSVC_URL=\\\"https://opax-mcp-zjbqp3prqq-uc.a.run.app\\\"\\necho \\\"Service URL: $SVC_URL\\\"\\nTOKEN=$(curl -s -H \\\"Metadata-Flavor: Google\\\" \\\\\\n \\\"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=$SVC_URL&format=full\\\")\\nSTATUS=$(curl -s -o /dev/null -w \\\"%{http_code}\\\" \\\\\\n -H \\\"Authorization: Bearer $TOKEN\\\" \\\"$SVC_URL/health\\\" 2>/dev/null || echo \\\"000\\\")\\necho \\\"Health status: $STATUS\\\"\\n[ \\\"$STATUS\\\" = \\\"200\\\" ] && echo \\\"OK\\\" || echo \\\"WARN: health check returnerte $STATUS\\\"\\necho \\\"=== Smoke Test ferdig ===\\\"\\n\"], \"entrypoint\": \"bash\", \"id\": \"smoke-test\", \"name\": \"gcr.io/google.com/cloudsdktool/cloud-sdk\", \"pullTiming\": {\"endTime\": \"2026-07-21T09:59:40.269855927Z\", \"startTime\": \"2026-07-21T09:59:40.267915475Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T09:59:41.165331614Z\", \"startTime\": \"2026-07-21T09:59:40.267915475Z\"}, \"waitFor\": [\"deploy\"]}, {\"args\": [\"-c\", \"WH_URL=$(gcloud secrets versions access latest \\\\\\n --secret=webhook-url --project=propane-will-491900-m5 2>/dev/null || echo \\\"\\\")\\nif [[ -n \\\"$WH_URL\\\" ]]; then\\n MSG=\\\"✅ opax-mcp deploy fullført\\\\nCommit: \\\\nService: opax-mcp (us-central1)\\\\nhttps://opax-mcp-zjbqp3prqq-uc.a.run.app\\\"\\n curl -s -X POST \\\"$WH_URL\\\" \\\\\\n -H 'Content-Type: application/json' \\\\\\n -d \\\"{\\\\\\\"text\\\\\\\": \\\\\\\"$MSG\\\\\\\"}\\\" || true\\n echo \\\"✓ Varsel sendt\\\"\\nelse\\n echo \\\"⚠️ Ingen webhook-url — hopper over varsling\\\"\\nfi\\n\"], \"entrypoint\": \"bash\", \"id\": \"notify\", \"name\": \"gcr.io/google.com/cloudsdktool/cloud-sdk\", \"pullTiming\": {\"endTime\": \"2026-07-21T09:59:41.166874392Z\", \"startTime\": \"2026-07-21T09:59:41.165434515Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T09:59:43.713927598Z\", \"startTime\": \"2026-07-21T09:59:41.165434515Z\"}, \"waitFor\": [\"smoke-test\"]}], \"substitutions\": {\"_ARTIFACT_REPO\": \"osvauco-repo\", \"_MCP_SA\": \"jason-vauger@propane-will-491900-m5.iam.gserviceaccount.com\", \"_REGION\": \"us-central1\"}, \"timeout\": \"3600s\", \"timing\": {\"BUILD\": {\"endTime\": \"2026-07-21T09:59:43.714038614Z\", \"startTime\": \"2026-07-21T09:53:40.178203463Z\"}, \"FETCHSOURCE\": {\"endTime\": \"2026-07-21T09:53:40.178106981Z\", \"startTime\": \"2026-07-21T09:53:37.125691882Z\"}, \"STORAGE_SOURCE\": {\"endTime\": \"2026-07-21T09:53:40.178106981Z\", \"startTime\": \"2026-07-21T09:53:37.125691882Z\"}}}, {\"createTime\": \"2026-07-21T09:37:41.971759055Z\", \"finishTime\": \"2026-07-21T09:43:53.403501Z\", \"id\": \"ae990270-c9db-41be-8cc3-14fbb07a5e15\", \"logUrl\": \"https://console.cloud.google.com/cloud-build/builds/ae990270-c9db-41be-8cc3-14fbb07a5e15?project=357036551735\", \"name\": \"projects/357036551735/locations/global/builds/ae990270-c9db-41be-8cc3-14fbb07a5e15\", \"options\": {\"logging\": \"CLOUD_LOGGING_ONLY\", \"pool\": {}, \"substitutionOption\": \"ALLOW_LOOSE\"}, \"projectId\": \"propane-will-491900-m5\", \"queueTtl\": \"3600s\", \"results\": {\"buildStepImages\": [\"sha256:77418e6e7c7f434c4a98eaff04ef16840cf03649c881c03948e3e213923e3136\", \"sha256:6c9b879570fe1c63a78af0b575ca5ac52f6c2c7e25f76f91ae1f2d6cb2a872ee\", \"sha256:6c9b879570fe1c63a78af0b575ca5ac52f6c2c7e25f76f91ae1f2d6cb2a872ee\", \"sha256:934ae3cc409975d61148e7aa3705ac3676f65b8ff17ad883f731881d77c9b92d\", \"sha256:934ae3cc409975d61148e7aa3705ac3676f65b8ff17ad883f731881d77c9b92d\", \"sha256:934ae3cc409975d61148e7aa3705ac3676f65b8ff17ad883f731881d77c9b92d\"], \"buildStepOutputs\": [\"\", \"\", \"\", \"\", \"\", \"\"], \"buildStepResults\": {\"build\": {}, \"clone\": {}, \"deploy\": {}, \"notify\": {}, \"push\": {}, \"smoke-test\": {}}}, \"serviceAccount\": \"projects/propane-will-491900-m5/serviceAccounts/357036551735-compute@developer.gserviceaccount.com\", \"source\": {\"storageSource\": {\"bucket\": \"propane-will-491900-m5_cloudbuild\", \"generation\": \"1784626661775541\", \"object\": \"source/1784626661.16816-98093c1d59d64e2184f4cfb24cef3655.tgz\"}}, \"sourceProvenance\": {\"fileHashes\": {\"gs://propane-will-491900-m5_cloudbuild/source/1784626661.16816-98093c1d59d64e2184f4cfb24cef3655.tgz#1784626661775541\": {\"fileHash\": [{\"type\": \"MD5\", \"value\": \"3g0fSH8SOmeAqPF84iIlXA==\"}, {\"type\": \"SHA256\", \"value\": \"Hf1CQR8KcO2-acAUPAxdKcAHcXzdIYNSXffr1tMVSXU=\"}]}}, \"resolvedStorageSource\": {\"bucket\": \"propane-will-491900-m5_cloudbuild\", \"generation\": \"1784626661775541\", \"object\": \"source/1784626661.16816-98093c1d59d64e2184f4cfb24cef3655.tgz\"}}, \"startTime\": \"2026-07-21T09:37:42.803100703Z\", \"status\": \"SUCCESS\", \"steps\": [{\"args\": [\"-c\", \"git clone --depth=1 --branch=main \\\\\\n http://34.170.51.84:3000/chris/osvauco.git /workspace/src\\ncp -r /workspace/src/. /workspace/\\n\"], \"entrypoint\": \"sh\", \"id\": \"clone\", \"name\": \"alpine/git\", \"pullTiming\": {\"endTime\": \"2026-07-21T09:37:54.549033020Z\", \"startTime\": \"2026-07-21T09:37:49.152546916Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T09:37:55.808901174Z\", \"startTime\": \"2026-07-21T09:37:49.152546916Z\"}}, {\"args\": [\"build\", \"--cache-from\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:latest\", \"-t\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:ae990270-c9db-41be-8cc3-14fbb07a5e15\", \"-t\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:latest\", \"opax-mcp\", \"-f\", \"opax-mcp/Dockerfile\"], \"id\": \"build\", \"name\": \"gcr.io/cloud-builders/docker\", \"pullTiming\": {\"endTime\": \"2026-07-21T09:37:55.816393003Z\", \"startTime\": \"2026-07-21T09:37:55.809000968Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T09:40:04.003711524Z\", \"startTime\": \"2026-07-21T09:37:55.809000968Z\"}}, {\"args\": [\"push\", \"--all-tags\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp\"], \"id\": \"push\", \"name\": \"gcr.io/cloud-builders/docker\", \"pullTiming\": {\"endTime\": \"2026-07-21T09:40:04.005474526Z\", \"startTime\": \"2026-07-21T09:40:04.003842048Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T09:40:53.315661515Z\", \"startTime\": \"2026-07-21T09:40:04.003842048Z\"}, \"waitFor\": [\"build\"]}, {\"args\": [\"run\", \"deploy\", \"opax-mcp\", \"--image=us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:ae990270-c9db-41be-8cc3-14fbb07a5e15\", \"--region=us-central1\", \"--project=propane-will-491900-m5\", \"--service-account=jason-vauger@propane-will-491900-m5.iam.gserviceaccount.com\", \"--set-env-vars=GOOGLE_CLOUD_PROJECT=propane-will-491900-m5,OPAX_BASE_URL=https://opax.vauco.no\", \"--update-secrets=MCP_SECRET=mcp-server-key:latest\", \"--allow-unauthenticated\", \"--port=8080\", \"--memory=512Mi\", \"--cpu=1\", \"--min-instances=0\", \"--max-instances=2\", \"--quiet\"], \"entrypoint\": \"gcloud\", \"id\": \"deploy\", \"name\": \"gcr.io/google.com/cloudsdktool/cloud-sdk\", \"pullTiming\": {\"endTime\": \"2026-07-21T09:42:06.401067750Z\", \"startTime\": \"2026-07-21T09:40:53.315874559Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T09:43:49.358450967Z\", \"startTime\": \"2026-07-21T09:40:53.315874559Z\"}, \"waitFor\": [\"push\"]}, {\"args\": [\"-c\", \"echo \\\"=== MCP Smoke Test ===\\\"\\nSVC_URL=\\\"https://opax-mcp-zjbqp3prqq-uc.a.run.app\\\"\\necho \\\"Service URL: $SVC_URL\\\"\\nTOKEN=$(curl -s -H \\\"Metadata-Flavor: Google\\\" \\\\\\n \\\"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=$SVC_URL&format=full\\\")\\nSTATUS=$(curl -s -o /dev/null -w \\\"%{http_code}\\\" \\\\\\n -H \\\"Authorization: Bearer $TOKEN\\\" \\\"$SVC_URL/health\\\" 2>/dev/null || echo \\\"000\\\")\\necho \\\"Health status: $STATUS\\\"\\n[ \\\"$STATUS\\\" = \\\"200\\\" ] && echo \\\"OK\\\" || echo \\\"WARN: health check returnerte $STATUS\\\"\\necho \\\"=== Smoke Test ferdig ===\\\"\\n\"], \"entrypoint\": \"bash\", \"id\": \"smoke-test\", \"name\": \"gcr.io/google.com/cloudsdktool/cloud-sdk\", \"pullTiming\": {\"endTime\": \"2026-07-21T09:43:49.360376905Z\", \"startTime\": \"2026-07-21T09:43:49.358567737Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T09:43:50.343165531Z\", \"startTime\": \"2026-07-21T09:43:49.358567737Z\"}, \"waitFor\": [\"deploy\"]}, {\"args\": [\"-c\", \"WH_URL=$(gcloud secrets versions access latest \\\\\\n --secret=webhook-url --project=propane-will-491900-m5 2>/dev/null || echo \\\"\\\")\\nif [[ -n \\\"$WH_URL\\\" ]]; then\\n MSG=\\\"✅ opax-mcp deploy fullført\\\\nCommit: \\\\nService: opax-mcp (us-central1)\\\\nhttps://opax-mcp-zjbqp3prqq-uc.a.run.app\\\"\\n curl -s -X POST \\\"$WH_URL\\\" \\\\\\n -H 'Content-Type: application/json' \\\\\\n -d \\\"{\\\\\\\"text\\\\\\\": \\\\\\\"$MSG\\\\\\\"}\\\" || true\\n echo \\\"✓ Varsel sendt\\\"\\nelse\\n echo \\\"⚠️ Ingen webhook-url — hopper over varsling\\\"\\nfi\\n\"], \"entrypoint\": \"bash\", \"id\": \"notify\", \"name\": \"gcr.io/google.com/cloudsdktool/cloud-sdk\", \"pullTiming\": {\"endTime\": \"2026-07-21T09:43:50.344864811Z\", \"startTime\": \"2026-07-21T09:43:50.343279111Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T09:43:52.955760277Z\", \"startTime\": \"2026-07-21T09:43:50.343279111Z\"}, \"waitFor\": [\"smoke-test\"]}], \"substitutions\": {\"_ARTIFACT_REPO\": \"osvauco-repo\", \"_MCP_SA\": \"jason-vauger@propane-will-491900-m5.iam.gserviceaccount.com\", \"_REGION\": \"us-central1\"}, \"timeout\": \"3600s\", \"timing\": {\"BUILD\": {\"endTime\": \"2026-07-21T09:43:52.955862849Z\", \"startTime\": \"2026-07-21T09:37:48.465872580Z\"}, \"FETCHSOURCE\": {\"endTime\": \"2026-07-21T09:37:48.465764042Z\", \"startTime\": \"2026-07-21T09:37:45.281197812Z\"}, \"STORAGE_SOURCE\": {\"endTime\": \"2026-07-21T09:37:48.465764042Z\", \"startTime\": \"2026-07-21T09:37:45.281197812Z\"}}}, {\"createTime\": \"2026-07-21T08:23:06.611669082Z\", \"finishTime\": \"2026-07-21T08:28:59.851716Z\", \"id\": \"1382a03e-41b9-48bb-9b09-5fb5f76a2eb4\", \"logUrl\": \"https://console.cloud.google.com/cloud-build/builds/1382a03e-41b9-48bb-9b09-5fb5f76a2eb4?project=357036551735\", \"name\": \"projects/357036551735/locations/global/builds/1382a03e-41b9-48bb-9b09-5fb5f76a2eb4\", \"options\": {\"logging\": \"CLOUD_LOGGING_ONLY\", \"pool\": {}, \"substitutionOption\": \"ALLOW_LOOSE\"}, \"projectId\": \"propane-will-491900-m5\", \"queueTtl\": \"3600s\", \"results\": {\"buildStepImages\": [\"sha256:77418e6e7c7f434c4a98eaff04ef16840cf03649c881c03948e3e213923e3136\", \"sha256:6c9b879570fe1c63a78af0b575ca5ac52f6c2c7e25f76f91ae1f2d6cb2a872ee\", \"sha256:6c9b879570fe1c63a78af0b575ca5ac52f6c2c7e25f76f91ae1f2d6cb2a872ee\", \"sha256:934ae3cc409975d61148e7aa3705ac3676f65b8ff17ad883f731881d77c9b92d\", \"sha256:934ae3cc409975d61148e7aa3705ac3676f65b8ff17ad883f731881d77c9b92d\", \"sha256:934ae3cc409975d61148e7aa3705ac3676f65b8ff17ad883f731881d77c9b92d\"], \"buildStepOutputs\": [\"\", \"\", \"\", \"\", \"\", \"\"], \"buildStepResults\": {\"build\": {}, \"clone\": {}, \"deploy\": {}, \"notify\": {}, \"push\": {}, \"smoke-test\": {}}}, \"serviceAccount\": \"projects/propane-will-491900-m5/serviceAccounts/357036551735-compute@developer.gserviceaccount.com\", \"source\": {\"storageSource\": {\"bucket\": \"propane-will-491900-m5_cloudbuild\", \"generation\": \"1784622186384081\", \"object\": \"source/1784622185.771741-fe81ac6e04a546da850fb89980009cac.tgz\"}}, \"sourceProvenance\": {\"fileHashes\": {\"gs://propane-will-491900-m5_cloudbuild/source/1784622185.771741-fe81ac6e04a546da850fb89980009cac.tgz#1784622186384081\": {\"fileHash\": [{\"type\": \"MD5\", \"value\": \"V96Zmwbg3PebER_3BN1kYw==\"}, {\"type\": \"SHA256\", \"value\": \"JNJcREH1XTr224Xa0DU6hfOCQnhHT-unOjGv9O43_Cw=\"}]}}, \"resolvedStorageSource\": {\"bucket\": \"propane-will-491900-m5_cloudbuild\", \"generation\": \"1784622186384081\", \"object\": \"source/1784622185.771741-fe81ac6e04a546da850fb89980009cac.tgz\"}}, \"startTime\": \"2026-07-21T08:23:07.500680285Z\", \"status\": \"SUCCESS\", \"steps\": [{\"args\": [\"-c\", \"git clone --depth=1 --branch=main \\\\\\n http://34.170.51.84:3000/chris/osvauco.git /workspace/src\\ncp -r /workspace/src/. /workspace/\\n\"], \"entrypoint\": \"sh\", \"id\": \"clone\", \"name\": \"alpine/git\", \"pullTiming\": {\"endTime\": \"2026-07-21T08:23:18.622417975Z\", \"startTime\": \"2026-07-21T08:23:13.429552552Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T08:23:19.865414434Z\", \"startTime\": \"2026-07-21T08:23:13.429552552Z\"}}, {\"args\": [\"build\", \"--cache-from\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:latest\", \"-t\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:1382a03e-41b9-48bb-9b09-5fb5f76a2eb4\", \"-t\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:latest\", \"opax-mcp\", \"-f\", \"opax-mcp/Dockerfile\"], \"id\": \"build\", \"name\": \"gcr.io/cloud-builders/docker\", \"pullTiming\": {\"endTime\": \"2026-07-21T08:23:19.871561495Z\", \"startTime\": \"2026-07-21T08:23:19.865556316Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T08:25:27.544216241Z\", \"startTime\": \"2026-07-21T08:23:19.865556316Z\"}}, {\"args\": [\"push\", \"--all-tags\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp\"], \"id\": \"push\", \"name\": \"gcr.io/cloud-builders/docker\", \"pullTiming\": {\"endTime\": \"2026-07-21T08:25:27.546477837Z\", \"startTime\": \"2026-07-21T08:25:27.544317879Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T08:26:10.849228123Z\", \"startTime\": \"2026-07-21T08:25:27.544317879Z\"}, \"waitFor\": [\"build\"]}, {\"args\": [\"run\", \"deploy\", \"opax-mcp\", \"--image=us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:1382a03e-41b9-48bb-9b09-5fb5f76a2eb4\", \"--region=us-central1\", \"--project=propane-will-491900-m5\", \"--service-account=jason-vauger@propane-will-491900-m5.iam.gserviceaccount.com\", \"--set-env-vars=GOOGLE_CLOUD_PROJECT=propane-will-491900-m5,OPAX_BASE_URL=https://opax.vauco.no\", \"--set-secrets=MCP_SECRET=mcp-server-key:latest\", \"--no-allow-unauthenticated\", \"--port=8080\", \"--memory=512Mi\", \"--cpu=1\", \"--min-instances=0\", \"--max-instances=2\", \"--quiet\"], \"entrypoint\": \"gcloud\", \"id\": \"deploy\", \"name\": \"gcr.io/google.com/cloudsdktool/cloud-sdk\", \"pullTiming\": {\"endTime\": \"2026-07-21T08:27:20.315189927Z\", \"startTime\": \"2026-07-21T08:26:10.849354075Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T08:28:55.940711692Z\", \"startTime\": \"2026-07-21T08:26:10.849354075Z\"}, \"waitFor\": [\"push\"]}, {\"args\": [\"-c\", \"echo \\\"=== MCP Smoke Test ===\\\"\\nSVC_URL=\\\"https://opax-mcp-zjbqp3prqq-uc.a.run.app\\\"\\necho \\\"Service URL: $SVC_URL\\\"\\nTOKEN=$(curl -s -H \\\"Metadata-Flavor: Google\\\" \\\\\\n \\\"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=$SVC_URL&format=full\\\")\\nSTATUS=$(curl -s -o /dev/null -w \\\"%{http_code}\\\" \\\\\\n -H \\\"Authorization: Bearer $TOKEN\\\" \\\"$SVC_URL/health\\\" 2>/dev/null || echo \\\"000\\\")\\necho \\\"Health status: $STATUS\\\"\\n[ \\\"$STATUS\\\" = \\\"200\\\" ] && echo \\\"OK\\\" || echo \\\"WARN: health check returnerte $STATUS\\\"\\necho \\\"=== Smoke Test ferdig ===\\\"\\n\"], \"entrypoint\": \"bash\", \"id\": \"smoke-test\", \"name\": \"gcr.io/google.com/cloudsdktool/cloud-sdk\", \"pullTiming\": {\"endTime\": \"2026-07-21T08:28:55.942332233Z\", \"startTime\": \"2026-07-21T08:28:55.940819534Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T08:28:56.988014174Z\", \"startTime\": \"2026-07-21T08:28:55.940819534Z\"}, \"waitFor\": [\"deploy\"]}, {\"args\": [\"-c\", \"WH_URL=$(gcloud secrets versions access latest \\\\\\n --secret=webhook-url --project=propane-will-491900-m5 2>/dev/null || echo \\\"\\\")\\nif [[ -n \\\"$WH_URL\\\" ]]; then\\n MSG=\\\"✅ opax-mcp deploy fullført\\\\nCommit: \\\\nService: opax-mcp (us-central1)\\\\nhttps://opax-mcp-zjbqp3prqq-uc.a.run.app\\\"\\n curl -s -X POST \\\"$WH_URL\\\" \\\\\\n -H 'Content-Type: application/json' \\\\\\n -d \\\"{\\\\\\\"text\\\\\\\": \\\\\\\"$MSG\\\\\\\"}\\\" || true\\n echo \\\"✓ Varsel sendt\\\"\\nelse\\n echo \\\"⚠️ Ingen webhook-url — hopper over varsling\\\"\\nfi\\n\"], \"entrypoint\": \"bash\", \"id\": \"notify\", \"name\": \"gcr.io/google.com/cloudsdktool/cloud-sdk\", \"pullTiming\": {\"endTime\": \"2026-07-21T08:28:56.989638063Z\", \"startTime\": \"2026-07-21T08:28:56.988124682Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T08:28:59.406962491Z\", \"startTime\": \"2026-07-21T08:28:56.988124682Z\"}, \"waitFor\": [\"smoke-test\"]}], \"substitutions\": {\"_ARTIFACT_REPO\": \"osvauco-repo\", \"_MCP_SA\": \"jason-vauger@propane-will-491900-m5.iam.gserviceaccount.com\", \"_REGION\": \"us-central1\"}, \"timeout\": \"3600s\", \"timing\": {\"BUILD\": {\"endTime\": \"2026-07-21T08:28:59.407056925Z\", \"startTime\": \"2026-07-21T08:23:12.782535784Z\"}, \"FETCHSOURCE\": {\"endTime\": \"2026-07-21T08:23:12.782173559Z\", \"startTime\": \"2026-07-21T08:23:09.573789067Z\"}, \"STORAGE_SOURCE\": {\"endTime\": \"2026-07-21T08:23:12.782173559Z\", \"startTime\": \"2026-07-21T08:23:09.573789067Z\"}}}, {\"createTime\": \"2026-07-21T05:14:09.322793343Z\", \"finishTime\": \"2026-07-21T05:20:36.076062Z\", \"id\": \"afba88ba-1acc-4fa2-999c-265e42e00659\", \"logUrl\": \"https://console.cloud.google.com/cloud-build/builds/afba88ba-1acc-4fa2-999c-265e42e00659?project=357036551735\", \"name\": \"projects/357036551735/locations/global/builds/afba88ba-1acc-4fa2-999c-265e42e00659\", \"options\": {\"logging\": \"CLOUD_LOGGING_ONLY\", \"pool\": {}, \"substitutionOption\": \"ALLOW_LOOSE\"}, \"projectId\": \"propane-will-491900-m5\", \"queueTtl\": \"3600s\", \"results\": {\"buildStepImages\": [\"sha256:77418e6e7c7f434c4a98eaff04ef16840cf03649c881c03948e3e213923e3136\", \"sha256:6c9b879570fe1c63a78af0b575ca5ac52f6c2c7e25f76f91ae1f2d6cb2a872ee\", \"sha256:6c9b879570fe1c63a78af0b575ca5ac52f6c2c7e25f76f91ae1f2d6cb2a872ee\", \"sha256:934ae3cc409975d61148e7aa3705ac3676f65b8ff17ad883f731881d77c9b92d\", \"sha256:934ae3cc409975d61148e7aa3705ac3676f65b8ff17ad883f731881d77c9b92d\", \"sha256:934ae3cc409975d61148e7aa3705ac3676f65b8ff17ad883f731881d77c9b92d\"], \"buildStepOutputs\": [\"\", \"\", \"\", \"\", \"\", \"\"], \"buildStepResults\": {\"build\": {}, \"clone\": {}, \"deploy\": {}, \"notify\": {}, \"push\": {}, \"smoke-test\": {}}}, \"serviceAccount\": \"projects/propane-will-491900-m5/serviceAccounts/357036551735-compute@developer.gserviceaccount.com\", \"source\": {\"storageSource\": {\"bucket\": \"propane-will-491900-m5_cloudbuild\", \"generation\": \"1784610849100084\", \"object\": \"source/1784610848.46713-e33c9ecb6d334d5ca87ad06420a1eef3.tgz\"}}, \"sourceProvenance\": {\"fileHashes\": {\"gs://propane-will-491900-m5_cloudbuild/source/1784610848.46713-e33c9ecb6d334d5ca87ad06420a1eef3.tgz#1784610849100084\": {\"fileHash\": [{\"type\": \"MD5\", \"value\": \"ECyGFs-mR17IUsBq6G1x2A==\"}, {\"type\": \"SHA256\", \"value\": \"MC5pJNiGGTuF4ZcB2AIP8_HxJFfOMHf3l_LGk3ePAyo=\"}]}}, \"resolvedStorageSource\": {\"bucket\": \"propane-will-491900-m5_cloudbuild\", \"generation\": \"1784610849100084\", \"object\": \"source/1784610848.46713-e33c9ecb6d334d5ca87ad06420a1eef3.tgz\"}}, \"startTime\": \"2026-07-21T05:14:10.272075460Z\", \"status\": \"SUCCESS\", \"steps\": [{\"args\": [\"-c\", \"git clone --depth=1 --branch=main \\\\\\n http://34.170.51.84:3000/chris/osvauco.git /workspace/src\\ncp -r /workspace/src/. /workspace/\\n\"], \"entrypoint\": \"sh\", \"id\": \"clone\", \"name\": \"alpine/git\", \"pullTiming\": {\"endTime\": \"2026-07-21T05:14:23.964670354Z\", \"startTime\": \"2026-07-21T05:14:18.663746609Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T05:14:25.310985852Z\", \"startTime\": \"2026-07-21T05:14:18.663746609Z\"}}, {\"args\": [\"build\", \"--cache-from\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:latest\", \"-t\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:afba88ba-1acc-4fa2-999c-265e42e00659\", \"-t\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:latest\", \"opax-mcp\", \"-f\", \"opax-mcp/Dockerfile\"], \"id\": \"build\", \"name\": \"gcr.io/cloud-builders/docker\", \"pullTiming\": {\"endTime\": \"2026-07-21T05:14:25.317118220Z\", \"startTime\": \"2026-07-21T05:14:25.311074091Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T05:16:32.390112375Z\", \"startTime\": \"2026-07-21T05:14:25.311074091Z\"}}, {\"args\": [\"push\", \"--all-tags\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp\"], \"id\": \"push\", \"name\": \"gcr.io/cloud-builders/docker\", \"pullTiming\": {\"endTime\": \"2026-07-21T05:16:32.391887433Z\", \"startTime\": \"2026-07-21T05:16:32.390367665Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T05:17:17.741346803Z\", \"startTime\": \"2026-07-21T05:16:32.390367665Z\"}, \"waitFor\": [\"build\"]}, {\"args\": [\"run\", \"deploy\", \"opax-mcp\", \"--image=us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:afba88ba-1acc-4fa2-999c-265e42e00659\", \"--region=us-central1\", \"--project=propane-will-491900-m5\", \"--service-account=jason-vauger@propane-will-491900-m5.iam.gserviceaccount.com\", \"--set-env-vars=GOOGLE_CLOUD_PROJECT=propane-will-491900-m5,OPAX_BASE_URL=https://opax.vauco.no\", \"--set-secrets=MCP_SECRET=mcp-server-key:latest\", \"--no-allow-unauthenticated\", \"--port=8080\", \"--memory=512Mi\", \"--cpu=1\", \"--min-instances=0\", \"--max-instances=2\", \"--quiet\"], \"entrypoint\": \"gcloud\", \"id\": \"deploy\", \"name\": \"gcr.io/google.com/cloudsdktool/cloud-sdk\", \"pullTiming\": {\"endTime\": \"2026-07-21T05:18:30.670887675Z\", \"startTime\": \"2026-07-21T05:17:17.741489349Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T05:20:31.783546896Z\", \"startTime\": \"2026-07-21T05:17:17.741489349Z\"}, \"waitFor\": [\"push\"]}, {\"args\": [\"-c\", \"echo \\\"=== MCP Smoke Test ===\\\"\\nSVC_URL=\\\"https://opax-mcp-zjbqp3prqq-uc.a.run.app\\\"\\necho \\\"Service URL: $SVC_URL\\\"\\nTOKEN=$(curl -s -H \\\"Metadata-Flavor: Google\\\" \\\\\\n \\\"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=$SVC_URL&format=full\\\")\\nSTATUS=$(curl -s -o /dev/null -w \\\"%{http_code}\\\" \\\\\\n -H \\\"Authorization: Bearer $TOKEN\\\" \\\"$SVC_URL/health\\\" 2>/dev/null || echo \\\"000\\\")\\necho \\\"Health status: $STATUS\\\"\\n[ \\\"$STATUS\\\" = \\\"200\\\" ] && echo \\\"OK\\\" || echo \\\"WARN: health check returnerte $STATUS\\\"\\necho \\\"=== Smoke Test ferdig ===\\\"\\n\"], \"entrypoint\": \"bash\", \"id\": \"smoke-test\", \"name\": \"gcr.io/google.com/cloudsdktool/cloud-sdk\", \"pullTiming\": {\"endTime\": \"2026-07-21T05:20:31.785602999Z\", \"startTime\": \"2026-07-21T05:20:31.783700601Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T05:20:32.840262700Z\", \"startTime\": \"2026-07-21T05:20:31.783700601Z\"}, \"waitFor\": [\"deploy\"]}, {\"args\": [\"-c\", \"WH_URL=$(gcloud secrets versions access latest \\\\\\n --secret=webhook-url --project=propane-will-491900-m5 2>/dev/null || echo \\\"\\\")\\nif [[ -n \\\"$WH_URL\\\" ]]; then\\n MSG=\\\"✅ opax-mcp deploy fullført\\\\nCommit: \\\\nService: opax-mcp (us-central1)\\\\nhttps://opax-mcp-zjbqp3prqq-uc.a.run.app\\\"\\n curl -s -X POST \\\"$WH_URL\\\" \\\\\\n -H 'Content-Type: application/json' \\\\\\n -d \\\"{\\\\\\\"text\\\\\\\": \\\\\\\"$MSG\\\\\\\"}\\\" || true\\n echo \\\"✓ Varsel sendt\\\"\\nelse\\n echo \\\"⚠️ Ingen webhook-url — hopper over varsling\\\"\\nfi\\n\"], \"entrypoint\": \"bash\", \"id\": \"notify\", \"name\": \"gcr.io/google.com/cloudsdktool/cloud-sdk\", \"pullTiming\": {\"endTime\": \"2026-07-21T05:20:32.842384078Z\", \"startTime\": \"2026-07-21T05:20:32.840399016Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T05:20:35.643509602Z\", \"startTime\": \"2026-07-21T05:20:32.840399016Z\"}, \"waitFor\": [\"smoke-test\"]}], \"substitutions\": {\"_ARTIFACT_REPO\": \"osvauco-repo\", \"_MCP_SA\": \"jason-vauger@propane-will-491900-m5.iam.gserviceaccount.com\", \"_REGION\": \"us-central1\"}, \"timeout\": \"3600s\", \"timing\": {\"BUILD\": {\"endTime\": \"2026-07-21T05:20:35.643657897Z\", \"startTime\": \"2026-07-21T05:14:17.945557705Z\"}, \"FETCHSOURCE\": {\"endTime\": \"2026-07-21T05:14:17.945275091Z\", \"startTime\": \"2026-07-21T05:14:14.486675617Z\"}, \"STORAGE_SOURCE\": {\"endTime\": \"2026-07-21T05:14:17.945275091Z\", \"startTime\": \"2026-07-21T05:14:14.486675617Z\"}}}, {\"createTime\": \"2026-07-21T05:02:13.481409827Z\", \"failureInfo\": {\"detail\": \"Build step failure: build step 3 \\\"gcr.io/google.com/cloudsdktool/cloud-sdk\\\" failed: step exited with non-zero status: 1\", \"type\": \"USER_BUILD_STEP\"}, \"finishTime\": \"2026-07-21T05:04:15.347902Z\", \"id\": \"497926d6-2a35-482f-aa85-1dae3a97bec8\", \"logUrl\": \"https://console.cloud.google.com/cloud-build/builds/497926d6-2a35-482f-aa85-1dae3a97bec8?project=357036551735\", \"name\": \"projects/357036551735/locations/global/builds/497926d6-2a35-482f-aa85-1dae3a97bec8\", \"options\": {\"logging\": \"CLOUD_LOGGING_ONLY\", \"pool\": {}, \"substitutionOption\": \"ALLOW_LOOSE\"}, \"projectId\": \"propane-will-491900-m5\", \"queueTtl\": \"3600s\", \"results\": {\"buildStepImages\": [\"sha256:77418e6e7c7f434c4a98eaff04ef16840cf03649c881c03948e3e213923e3136\", \"sha256:6c9b879570fe1c63a78af0b575ca5ac52f6c2c7e25f76f91ae1f2d6cb2a872ee\", \"sha256:6c9b879570fe1c63a78af0b575ca5ac52f6c2c7e25f76f91ae1f2d6cb2a872ee\", \"sha256:934ae3cc409975d61148e7aa3705ac3676f65b8ff17ad883f731881d77c9b92d\", \"\", \"\"], \"buildStepOutputs\": [\"\", \"\", \"\", \"\", \"\", \"\"], \"buildStepResults\": {\"build\": {}, \"clone\": {}, \"deploy\": {}, \"push\": {}}}, \"serviceAccount\": \"projects/propane-will-491900-m5/serviceAccounts/357036551735-compute@developer.gserviceaccount.com\", \"source\": {\"storageSource\": {\"bucket\": \"propane-will-491900-m5_cloudbuild\", \"generation\": \"1784610133268603\", \"object\": \"source/1784610132.634246-abe0932cce214ca0ae1e117f95304625.tgz\"}}, \"sourceProvenance\": {\"fileHashes\": {\"gs://propane-will-491900-m5_cloudbuild/source/1784610132.634246-abe0932cce214ca0ae1e117f95304625.tgz#1784610133268603\": {\"fileHash\": [{\"type\": \"MD5\", \"value\": \"KCkx2Wibo9jxAwG9wB_aRw==\"}, {\"type\": \"SHA256\", \"value\": \"XzfphAHZD9koLHXyPj0IhxOiE6DNdB2pD_xzmRapGmY=\"}]}}, \"resolvedStorageSource\": {\"bucket\": \"propane-will-491900-m5_cloudbuild\", \"generation\": \"1784610133268603\", \"object\": \"source/1784610132.634246-abe0932cce214ca0ae1e117f95304625.tgz\"}}, \"startTime\": \"2026-07-21T05:02:14.295542396Z\", \"status\": \"FAILURE\", \"steps\": [{\"args\": [\"-c\", \"git clone --depth=1 --branch=main \\\\\\n http://34.170.51.84:3000/chris/osvauco.git /workspace/src\\ncp -r /workspace/src/. /workspace/\\n\"], \"entrypoint\": \"sh\", \"id\": \"clone\", \"name\": \"alpine/git\", \"pullTiming\": {\"endTime\": \"2026-07-21T05:02:23.782928456Z\", \"startTime\": \"2026-07-21T05:02:20.459273935Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T05:02:24.859327270Z\", \"startTime\": \"2026-07-21T05:02:20.459273935Z\"}}, {\"args\": [\"build\", \"--cache-from\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:latest\", \"-t\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:497926d6-2a35-482f-aa85-1dae3a97bec8\", \"-t\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:latest\", \"opax-mcp\", \"-f\", \"opax-mcp/Dockerfile\"], \"id\": \"build\", \"name\": \"gcr.io/cloud-builders/docker\", \"pullTiming\": {\"endTime\": \"2026-07-21T05:02:24.866014814Z\", \"startTime\": \"2026-07-21T05:02:24.859446349Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T05:02:44.353638725Z\", \"startTime\": \"2026-07-21T05:02:24.859446349Z\"}}, {\"args\": [\"push\", \"--all-tags\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp\"], \"id\": \"push\", \"name\": \"gcr.io/cloud-builders/docker\", \"pullTiming\": {\"endTime\": \"2026-07-21T05:02:44.354802033Z\", \"startTime\": \"2026-07-21T05:02:44.353760104Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T05:02:55.608676136Z\", \"startTime\": \"2026-07-21T05:02:44.353760104Z\"}, \"waitFor\": [\"build\"]}, {\"args\": [\"run\", \"deploy\", \"opax-mcp\", \"--image=us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:497926d6-2a35-482f-aa85-1dae3a97bec8\", \"--region=us-central1\", \"--project=propane-will-491900-m5\", \"--service-account=jason-vauger@propane-will-491900-m5.iam.gserviceaccount.com\", \"--set-env-vars=GOOGLE_CLOUD_PROJECT=propane-will-491900-m5,OPAX_BASE_URL=https://opax.vauco.no\", \"--set-secrets=MCP_SECRET=mcp-server-key:latest\", \"--no-allow-unauthenticated\", \"--port=8080\", \"--memory=512Mi\", \"--cpu=1\", \"--min-instances=0\", \"--max-instances=2\", \"--quiet\"], \"entrypoint\": \"gcloud\", \"exitCode\": 1, \"id\": \"deploy\", \"name\": \"gcr.io/google.com/cloudsdktool/cloud-sdk\", \"pullTiming\": {\"endTime\": \"2026-07-21T05:03:47.543578317Z\", \"startTime\": \"2026-07-21T05:02:55.608755562Z\"}, \"status\": \"FAILURE\", \"timing\": {\"endTime\": \"2026-07-21T05:04:03.605542081Z\", \"startTime\": \"2026-07-21T05:02:55.608755562Z\"}, \"waitFor\": [\"push\"]}, {\"args\": [\"-c\", \"echo \\\"=== MCP Smoke Test ===\\\"\\nSVC_URL=\\\"https://opax-mcp-zjbqp3prqq-uc.a.run.app\\\"\\necho \\\"Service URL: $SVC_URL\\\"\\nTOKEN=$(curl -s -H \\\"Metadata-Flavor: Google\\\" \\\\\\n \\\"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=$SVC_URL&format=full\\\")\\nSTATUS=$(curl -s -o /dev/null -w \\\"%{http_code}\\\" \\\\\\n -H \\\"Authorization: Bearer $TOKEN\\\" \\\"$SVC_URL/health\\\" 2>/dev/null || echo \\\"000\\\")\\necho \\\"Health status: $STATUS\\\"\\n[ \\\"$STATUS\\\" = \\\"200\\\" ] && echo \\\"OK\\\" || echo \\\"WARN: health check returnerte $STATUS\\\"\\necho \\\"=== Smoke Test ferdig ===\\\"\\n\"], \"entrypoint\": \"bash\", \"id\": \"smoke-test\", \"name\": \"gcr.io/google.com/cloudsdktool/cloud-sdk\", \"status\": \"QUEUED\", \"waitFor\": [\"deploy\"]}, {\"args\": [\"-c\", \"WH_URL=$(gcloud secrets versions access latest \\\\\\n --secret=webhook-url --project=propane-will-491900-m5 2>/dev/null || echo \\\"\\\")\\nif [[ -n \\\"$WH_URL\\\" ]]; then\\n MSG=\\\"✅ opax-mcp deploy fullført\\\\nCommit: \\\\nService: opax-mcp (us-central1)\\\\nhttps://opax-mcp-zjbqp3prqq-uc.a.run.app\\\"\\n curl -s -X POST \\\"$WH_URL\\\" \\\\\\n -H 'Content-Type: application/json' \\\\\\n -d \\\"{\\\\\\\"text\\\\\\\": \\\\\\\"$MSG\\\\\\\"}\\\" || true\\n echo \\\"✓ Varsel sendt\\\"\\nelse\\n echo \\\"⚠️ Ingen webhook-url — hopper over varsling\\\"\\nfi\\n\"], \"entrypoint\": \"bash\", \"id\": \"notify\", \"name\": \"gcr.io/google.com/cloudsdktool/cloud-sdk\", \"status\": \"QUEUED\", \"waitFor\": [\"smoke-test\"]}], \"substitutions\": {\"_ARTIFACT_REPO\": \"osvauco-repo\", \"_MCP_SA\": \"jason-vauger@propane-will-491900-m5.iam.gserviceaccount.com\", \"_REGION\": \"us-central1\"}, \"timeout\": \"3600s\", \"timing\": {\"BUILD\": {\"endTime\": \"2026-07-21T05:04:03.605688532Z\", \"startTime\": \"2026-07-21T05:02:19.885958698Z\"}, \"FETCHSOURCE\": {\"endTime\": \"2026-07-21T05:02:19.885866251Z\", \"startTime\": \"2026-07-21T05:02:17.265531422Z\"}, \"STORAGE_SOURCE\": {\"endTime\": \"2026-07-21T05:02:19.885866251Z\", \"startTime\": \"2026-07-21T05:02:17.265531422Z\"}}}, {\"createTime\": \"2026-07-21T04:54:03.585468531Z\", \"failureInfo\": {\"detail\": \"Build step failure: build step 3 \\\"gcr.io/google.com/cloudsdktool/cloud-sdk\\\" failed: step exited with non-zero status: 1\", \"type\": \"USER_BUILD_STEP\"}, \"finishTime\": \"2026-07-21T04:56:18.057599Z\", \"id\": \"2b8a7f9c-b58a-4719-bf61-fe188c95c111\", \"logUrl\": \"https://console.cloud.google.com/cloud-build/builds/2b8a7f9c-b58a-4719-bf61-fe188c95c111?project=357036551735\", \"name\": \"projects/357036551735/locations/global/builds/2b8a7f9c-b58a-4719-bf61-fe188c95c111\", \"options\": {\"logging\": \"CLOUD_LOGGING_ONLY\", \"pool\": {}, \"substitutionOption\": \"ALLOW_LOOSE\"}, \"projectId\": \"propane-will-491900-m5\", \"queueTtl\": \"3600s\", \"results\": {\"buildStepImages\": [\"sha256:77418e6e7c7f434c4a98eaff04ef16840cf03649c881c03948e3e213923e3136\", \"sha256:6c9b879570fe1c63a78af0b575ca5ac52f6c2c7e25f76f91ae1f2d6cb2a872ee\", \"sha256:6c9b879570fe1c63a78af0b575ca5ac52f6c2c7e25f76f91ae1f2d6cb2a872ee\", \"sha256:934ae3cc409975d61148e7aa3705ac3676f65b8ff17ad883f731881d77c9b92d\", \"\", \"\"], \"buildStepOutputs\": [\"\", \"\", \"\", \"\", \"\", \"\"], \"buildStepResults\": {\"build\": {}, \"clone\": {}, \"deploy\": {}, \"push\": {}}}, \"serviceAccount\": \"projects/propane-will-491900-m5/serviceAccounts/357036551735-compute@developer.gserviceaccount.com\", \"source\": {\"storageSource\": {\"bucket\": \"propane-will-491900-m5_cloudbuild\", \"generation\": \"1784609643304363\", \"object\": \"source/1784609642.561932-9c66765cffa84b0783084b1cac354048.tgz\"}}, \"sourceProvenance\": {\"fileHashes\": {\"gs://propane-will-491900-m5_cloudbuild/source/1784609642.561932-9c66765cffa84b0783084b1cac354048.tgz#1784609643304363\": {\"fileHash\": [{\"type\": \"MD5\", \"value\": \"GUwf4sb_mDw_5rlkqDb6IA==\"}, {\"type\": \"SHA256\", \"value\": \"Q_bkm4F29NoGy4xIPXMbUJ9TgV8ut1awihepx56Qad0=\"}]}}, \"resolvedStorageSource\": {\"bucket\": \"propane-will-491900-m5_cloudbuild\", \"generation\": \"1784609643304363\", \"object\": \"source/1784609642.561932-9c66765cffa84b0783084b1cac354048.tgz\"}}, \"startTime\": \"2026-07-21T04:54:04.566670515Z\", \"status\": \"FAILURE\", \"steps\": [{\"args\": [\"-c\", \"git clone --depth=1 --branch=main \\\\\\n http://34.170.51.84:3000/chris/osvauco.git /workspace/src\\ncp -r /workspace/src/. /workspace/\\n\"], \"entrypoint\": \"sh\", \"id\": \"clone\", \"name\": \"alpine/git\", \"pullTiming\": {\"endTime\": \"2026-07-21T04:54:15.716701469Z\", \"startTime\": \"2026-07-21T04:54:12.825234542Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T04:54:16.711150422Z\", \"startTime\": \"2026-07-21T04:54:12.825234542Z\"}}, {\"args\": [\"build\", \"--cache-from\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:latest\", \"-t\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:2b8a7f9c-b58a-4719-bf61-fe188c95c111\", \"-t\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:latest\", \"opax-mcp\", \"-f\", \"opax-mcp/Dockerfile\"], \"id\": \"build\", \"name\": \"gcr.io/cloud-builders/docker\", \"pullTiming\": {\"endTime\": \"2026-07-21T04:54:16.715162344Z\", \"startTime\": \"2026-07-21T04:54:16.711249889Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T04:54:34.851842716Z\", \"startTime\": \"2026-07-21T04:54:16.711249889Z\"}}, {\"args\": [\"push\", \"--all-tags\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp\"], \"id\": \"push\", \"name\": \"gcr.io/cloud-builders/docker\", \"pullTiming\": {\"endTime\": \"2026-07-21T04:54:34.853033556Z\", \"startTime\": \"2026-07-21T04:54:34.852060432Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-21T04:54:42.708967159Z\", \"startTime\": \"2026-07-21T04:54:34.852060432Z\"}, \"waitFor\": [\"build\"]}, {\"args\": [\"run\", \"deploy\", \"opax-mcp\", \"--image=us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:2b8a7f9c-b58a-4719-bf61-fe188c95c111\", \"--region=us-central1\", \"--project=propane-will-491900-m5\", \"--service-account=jason-vauger@propane-will-491900-m5.iam.gserviceaccount.com\", \"--set-env-vars=GOOGLE_CLOUD_PROJECT=propane-will-491900-m5,OPAX_BASE_URL=https://opax.vauco.no\", \"--set-secrets=MCP_SECRET=mcp-server-key:latest\", \"--no-allow-unauthenticated\", \"--port=8080\", \"--memory=512Mi\", \"--cpu=1\", \"--min-instances=0\", \"--max-instances=2\", \"--quiet\"], \"entrypoint\": \"gcloud\", \"exitCode\": 1, \"id\": \"deploy\", \"name\": \"gcr.io/google.com/cloudsdktool/cloud-sdk\", \"pullTiming\": {\"endTime\": \"2026-07-21T04:55:34.391159320Z\", \"startTime\": \"2026-07-21T04:54:42.709048215Z\"}, \"status\": \"FAILURE\", \"timing\": {\"endTime\": \"2026-07-21T04:56:06.644119934Z\", \"startTime\": \"2026-07-21T04:54:42.709048215Z\"}, \"waitFor\": [\"push\"]}, {\"args\": [\"-c\", \"echo \\\"=== MCP Smoke Test ===\\\"\\nSVC_URL=\\\"https://opax-mcp-zjbqp3prqq-uc.a.run.app\\\"\\necho \\\"Service URL: $SVC_URL\\\"\\nTOKEN=$(curl -s -H \\\"Metadata-Flavor: Google\\\" \\\\\\n \\\"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=$SVC_URL&format=full\\\")\\nSTATUS=$(curl -s -o /dev/null -w \\\"%{http_code}\\\" \\\\\\n -H \\\"Authorization: Bearer $TOKEN\\\" \\\"$SVC_URL/health\\\" 2>/dev/null || echo \\\"000\\\")\\necho \\\"Health status: $STATUS\\\"\\n[ \\\"$STATUS\\\" = \\\"200\\\" ] && echo \\\"OK\\\" || echo \\\"WARN: health check returnerte $STATUS\\\"\\necho \\\"=== Smoke Test ferdig ===\\\"\\n\"], \"entrypoint\": \"bash\", \"id\": \"smoke-test\", \"name\": \"gcr.io/google.com/cloudsdktool/cloud-sdk\", \"status\": \"QUEUED\", \"waitFor\": [\"deploy\"]}, {\"args\": [\"-c\", \"WH_URL=$(gcloud secrets versions access latest \\\\\\n --secret=webhook-url --project=propane-will-491900-m5 2>/dev/null || echo \\\"\\\")\\nif [[ -n \\\"$WH_URL\\\" ]]; then\\n MSG=\\\"✅ opax-mcp deploy fullført\\\\nCommit: \\\\nService: opax-mcp (us-central1)\\\\nhttps://opax-mcp-zjbqp3prqq-uc.a.run.app\\\"\\n curl -s -X POST \\\"$WH_URL\\\" \\\\\\n -H 'Content-Type: application/json' \\\\\\n -d \\\"{\\\\\\\"text\\\\\\\": \\\\\\\"$MSG\\\\\\\"}\\\" || true\\n echo \\\"✓ Varsel sendt\\\"\\nelse\\n echo \\\"⚠️ Ingen webhook-url — hopper over varsling\\\"\\nfi\\n\"], \"entrypoint\": \"bash\", \"id\": \"notify\", \"name\": \"gcr.io/google.com/cloudsdktool/cloud-sdk\", \"status\": \"QUEUED\", \"waitFor\": [\"smoke-test\"]}], \"substitutions\": {\"_ARTIFACT_REPO\": \"osvauco-repo\", \"_MCP_SA\": \"jason-vauger@propane-will-491900-m5.iam.gserviceaccount.com\", \"_REGION\": \"us-central1\"}, \"timeout\": \"3600s\", \"timing\": {\"BUILD\": {\"endTime\": \"2026-07-21T04:56:06.644340436Z\", \"startTime\": \"2026-07-21T04:54:12.313062700Z\"}, \"FETCHSOURCE\": {\"endTime\": \"2026-07-21T04:54:12.312955552Z\", \"startTime\": \"2026-07-21T04:54:10.141979925Z\"}, \"STORAGE_SOURCE\": {\"endTime\": \"2026-07-21T04:54:12.312955552Z\", \"startTime\": \"2026-07-21T04:54:10.141979925Z\"}}}, {\"createTime\": \"2026-07-16T16:47:11.133819491Z\", \"finishTime\": \"2026-07-16T16:52:56.090954Z\", \"id\": \"20476f43-2905-48bf-b049-b2d9e568fd67\", \"logUrl\": \"https://console.cloud.google.com/cloud-build/builds/20476f43-2905-48bf-b049-b2d9e568fd67?project=357036551735\", \"logsBucket\": \"gs://357036551735.cloudbuild-logs.googleusercontent.com\", \"name\": \"projects/357036551735/locations/global/builds/20476f43-2905-48bf-b049-b2d9e568fd67\", \"options\": {\"logging\": \"LEGACY\", \"pool\": {}}, \"projectId\": \"propane-will-491900-m5\", \"queueTtl\": \"3600s\", \"results\": {\"buildStepImages\": [\"sha256:1a2d12db4fd104729db11e4ab2422c4fcb81ea61c656f57451295b3ff3c77abd\", \"sha256:1a2d12db4fd104729db11e4ab2422c4fcb81ea61c656f57451295b3ff3c77abd\", \"sha256:934ae3cc409975d61148e7aa3705ac3676f65b8ff17ad883f731881d77c9b92d\"], \"buildStepOutputs\": [\"\", \"\", \"\"], \"buildStepResults\": {\"build\": {}, \"deploy\": {}, \"push\": {}}}, \"serviceAccount\": \"projects/propane-will-491900-m5/serviceAccounts/357036551735-compute@developer.gserviceaccount.com\", \"source\": {\"storageSource\": {\"bucket\": \"propane-will-491900-m5_cloudbuild\", \"generation\": \"1784220430915122\", \"object\": \"source/1784220430.252274-bf5cb10771594a849f07c631e7938b46.tgz\"}}, \"sourceProvenance\": {\"fileHashes\": {\"gs://propane-will-491900-m5_cloudbuild/source/1784220430.252274-bf5cb10771594a849f07c631e7938b46.tgz#1784220430915122\": {\"fileHash\": [{\"type\": \"MD5\", \"value\": \"38J6rxhplqf9WvaynqSY_w==\"}, {\"type\": \"SHA256\", \"value\": \"ehr1dtxq1IZkHdTdkE7gliOQFX8lYRKBi6R7qlt0JNs=\"}]}}, \"resolvedStorageSource\": {\"bucket\": \"propane-will-491900-m5_cloudbuild\", \"generation\": \"1784220430915122\", \"object\": \"source/1784220430.252274-bf5cb10771594a849f07c631e7938b46.tgz\"}}, \"startTime\": \"2026-07-16T16:47:12.101721590Z\", \"status\": \"SUCCESS\", \"steps\": [{\"args\": [\"build\", \"-f\", \"opax-mcp/Dockerfile\", \"-t\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:20476f43-2905-48bf-b049-b2d9e568fd67\", \"opax-mcp\"], \"id\": \"build\", \"name\": \"gcr.io/cloud-builders/docker\", \"pullTiming\": {\"endTime\": \"2026-07-16T16:47:17.861152253Z\", \"startTime\": \"2026-07-16T16:47:17.853553237Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-16T16:49:22.944131810Z\", \"startTime\": \"2026-07-16T16:47:17.853553237Z\"}}, {\"args\": [\"push\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:20476f43-2905-48bf-b049-b2d9e568fd67\"], \"id\": \"push\", \"name\": \"gcr.io/cloud-builders/docker\", \"pullTiming\": {\"endTime\": \"2026-07-16T16:49:22.946149114Z\", \"startTime\": \"2026-07-16T16:49:22.944247420Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-16T16:50:05.883674151Z\", \"startTime\": \"2026-07-16T16:49:22.944247420Z\"}, \"waitFor\": [\"build\"]}, {\"args\": [\"run\", \"deploy\", \"opax-mcp\", \"--image=us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:20476f43-2905-48bf-b049-b2d9e568fd67\", \"--region=us-central1\"], \"entrypoint\": \"gcloud\", \"id\": \"deploy\", \"name\": \"gcr.io/google.com/cloudsdktool/cloud-sdk\", \"pullTiming\": {\"endTime\": \"2026-07-16T16:51:15.877323698Z\", \"startTime\": \"2026-07-16T16:50:05.883803752Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-16T16:52:55.389649083Z\", \"startTime\": \"2026-07-16T16:50:05.883803752Z\"}, \"waitFor\": [\"push\"]}], \"substitutions\": {\"_ARTIFACT_REPO\": \"osvauco-repo\", \"_REGION\": \"us-central1\"}, \"timeout\": \"3600s\", \"timing\": {\"BUILD\": {\"endTime\": \"2026-07-16T16:52:55.389747863Z\", \"startTime\": \"2026-07-16T16:47:17.217790180Z\"}, \"FETCHSOURCE\": {\"endTime\": \"2026-07-16T16:47:17.217670993Z\", \"startTime\": \"2026-07-16T16:47:14.138300472Z\"}, \"STORAGE_SOURCE\": {\"endTime\": \"2026-07-16T16:47:17.217670993Z\", \"startTime\": \"2026-07-16T16:47:14.138300472Z\"}}}, {\"createTime\": \"2026-07-16T16:17:58.164732233Z\", \"finishTime\": \"2026-07-16T16:20:19.437705Z\", \"id\": \"c9f12178-64d1-436b-ae0c-2f654ba64940\", \"logUrl\": \"https://console.cloud.google.com/cloud-build/builds/c9f12178-64d1-436b-ae0c-2f654ba64940?project=357036551735\", \"logsBucket\": \"gs://357036551735.cloudbuild-logs.googleusercontent.com\", \"name\": \"projects/357036551735/locations/global/builds/c9f12178-64d1-436b-ae0c-2f654ba64940\", \"options\": {\"logging\": \"LEGACY\", \"pool\": {}}, \"projectId\": \"propane-will-491900-m5\", \"queueTtl\": \"3600s\", \"results\": {\"buildStepImages\": [\"sha256:1a2d12db4fd104729db11e4ab2422c4fcb81ea61c656f57451295b3ff3c77abd\", \"sha256:1a2d12db4fd104729db11e4ab2422c4fcb81ea61c656f57451295b3ff3c77abd\", \"sha256:934ae3cc409975d61148e7aa3705ac3676f65b8ff17ad883f731881d77c9b92d\"], \"buildStepOutputs\": [\"\", \"\", \"\"], \"buildStepResults\": {\"build\": {}, \"deploy\": {}, \"push\": {}}}, \"serviceAccount\": \"projects/propane-will-491900-m5/serviceAccounts/357036551735-compute@developer.gserviceaccount.com\", \"source\": {\"storageSource\": {\"bucket\": \"propane-will-491900-m5_cloudbuild\", \"generation\": \"1784218677959989\", \"object\": \"source/1784218677.314785-f55d59f665b449bf88bdee92e9084bc1.tgz\"}}, \"sourceProvenance\": {\"fileHashes\": {\"gs://propane-will-491900-m5_cloudbuild/source/1784218677.314785-f55d59f665b449bf88bdee92e9084bc1.tgz#1784218677959989\": {\"fileHash\": [{\"type\": \"MD5\", \"value\": \"yT8PhZsO5JRnKi-EQMSYjg==\"}, {\"type\": \"SHA256\", \"value\": \"fHy9fKR_S-l9v0PyHVifTaGcJv9MRvxwFLSgh-6gH04=\"}]}}, \"resolvedStorageSource\": {\"bucket\": \"propane-will-491900-m5_cloudbuild\", \"generation\": \"1784218677959989\", \"object\": \"source/1784218677.314785-f55d59f665b449bf88bdee92e9084bc1.tgz\"}}, \"startTime\": \"2026-07-16T16:17:59.051120256Z\", \"status\": \"SUCCESS\", \"steps\": [{\"args\": [\"build\", \"-f\", \"opax-mcp/Dockerfile\", \"-t\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:c9f12178-64d1-436b-ae0c-2f654ba64940\", \"opax-mcp\"], \"id\": \"build\", \"name\": \"gcr.io/cloud-builders/docker\", \"pullTiming\": {\"endTime\": \"2026-07-16T16:18:04.516933582Z\", \"startTime\": \"2026-07-16T16:18:04.512374299Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-16T16:18:30.333639573Z\", \"startTime\": \"2026-07-16T16:18:04.512374299Z\"}}, {\"args\": [\"push\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:c9f12178-64d1-436b-ae0c-2f654ba64940\"], \"id\": \"push\", \"name\": \"gcr.io/cloud-builders/docker\", \"pullTiming\": {\"endTime\": \"2026-07-16T16:18:30.346444250Z\", \"startTime\": \"2026-07-16T16:18:30.333748781Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-16T16:18:44.670388926Z\", \"startTime\": \"2026-07-16T16:18:30.333748781Z\"}, \"waitFor\": [\"build\"]}, {\"args\": [\"run\", \"deploy\", \"opax-mcp\", \"--image=us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:c9f12178-64d1-436b-ae0c-2f654ba64940\", \"--region=us-central1\"], \"entrypoint\": \"gcloud\", \"id\": \"deploy\", \"name\": \"gcr.io/google.com/cloudsdktool/cloud-sdk\", \"pullTiming\": {\"endTime\": \"2026-07-16T16:19:59.631739248Z\", \"startTime\": \"2026-07-16T16:18:44.670495130Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-16T16:20:18.691534195Z\", \"startTime\": \"2026-07-16T16:18:44.670495130Z\"}, \"waitFor\": [\"push\"]}], \"substitutions\": {\"_ARTIFACT_REPO\": \"osvauco-repo\", \"_REGION\": \"us-central1\"}, \"timeout\": \"3600s\", \"timing\": {\"BUILD\": {\"endTime\": \"2026-07-16T16:20:18.691642424Z\", \"startTime\": \"2026-07-16T16:18:03.889676065Z\"}, \"FETCHSOURCE\": {\"endTime\": \"2026-07-16T16:18:03.889579554Z\", \"startTime\": \"2026-07-16T16:18:01.041573577Z\"}, \"STORAGE_SOURCE\": {\"endTime\": \"2026-07-16T16:18:03.889579554Z\", \"startTime\": \"2026-07-16T16:18:01.041573577Z\"}}}, {\"createTime\": \"2026-07-16T14:51:02.159921566Z\", \"finishTime\": \"2026-07-16T14:53:38.463085Z\", \"id\": \"f70b30d9-e521-4aaa-a6d2-4742382e8735\", \"logUrl\": \"https://console.cloud.google.com/cloud-build/builds/f70b30d9-e521-4aaa-a6d2-4742382e8735?project=357036551735\", \"logsBucket\": \"gs://357036551735.cloudbuild-logs.googleusercontent.com\", \"name\": \"projects/357036551735/locations/global/builds/f70b30d9-e521-4aaa-a6d2-4742382e8735\", \"options\": {\"logging\": \"LEGACY\", \"pool\": {}}, \"projectId\": \"propane-will-491900-m5\", \"queueTtl\": \"3600s\", \"results\": {\"buildStepImages\": [\"sha256:1a2d12db4fd104729db11e4ab2422c4fcb81ea61c656f57451295b3ff3c77abd\", \"sha256:1a2d12db4fd104729db11e4ab2422c4fcb81ea61c656f57451295b3ff3c77abd\", \"sha256:934ae3cc409975d61148e7aa3705ac3676f65b8ff17ad883f731881d77c9b92d\"], \"buildStepOutputs\": [\"\", \"\", \"\"], \"buildStepResults\": {\"build\": {}, \"deploy\": {}, \"push\": {}}}, \"serviceAccount\": \"projects/propane-will-491900-m5/serviceAccounts/357036551735-compute@developer.gserviceaccount.com\", \"source\": {\"storageSource\": {\"bucket\": \"propane-will-491900-m5_cloudbuild\", \"generation\": \"1784213461958384\", \"object\": \"source/1784213461.316852-b8fce743845347d8ac879e7f0bc31990.tgz\"}}, \"sourceProvenance\": {\"fileHashes\": {\"gs://propane-will-491900-m5_cloudbuild/source/1784213461.316852-b8fce743845347d8ac879e7f0bc31990.tgz#1784213461958384\": {\"fileHash\": [{\"type\": \"MD5\", \"value\": \"NXdqnTm3d6dnR5Sr_1xEhA==\"}, {\"type\": \"SHA256\", \"value\": \"QJvr7YiaxHojSJE4uXPTyeYM8Uy6qkaKFnz_X-5erfw=\"}]}}, \"resolvedStorageSource\": {\"bucket\": \"propane-will-491900-m5_cloudbuild\", \"generation\": \"1784213461958384\", \"object\": \"source/1784213461.316852-b8fce743845347d8ac879e7f0bc31990.tgz\"}}, \"startTime\": \"2026-07-16T14:51:03.012841803Z\", \"status\": \"SUCCESS\", \"steps\": [{\"args\": [\"build\", \"-f\", \"opax-mcp/Dockerfile\", \"-t\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:f70b30d9-e521-4aaa-a6d2-4742382e8735\", \"opax-mcp\"], \"id\": \"build\", \"name\": \"gcr.io/cloud-builders/docker\", \"pullTiming\": {\"endTime\": \"2026-07-16T14:51:10.725955796Z\", \"startTime\": \"2026-07-16T14:51:10.712481128Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-16T14:51:39.875896363Z\", \"startTime\": \"2026-07-16T14:51:10.712481128Z\"}}, {\"args\": [\"push\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:f70b30d9-e521-4aaa-a6d2-4742382e8735\"], \"id\": \"push\", \"name\": \"gcr.io/cloud-builders/docker\", \"pullTiming\": {\"endTime\": \"2026-07-16T14:51:39.878064653Z\", \"startTime\": \"2026-07-16T14:51:39.876153458Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-16T14:51:53.384469713Z\", \"startTime\": \"2026-07-16T14:51:39.876153458Z\"}, \"waitFor\": [\"build\"]}, {\"args\": [\"run\", \"deploy\", \"opax-mcp\", \"--image=us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:f70b30d9-e521-4aaa-a6d2-4742382e8735\", \"--region=us-central1\"], \"entrypoint\": \"gcloud\", \"id\": \"deploy\", \"name\": \"gcr.io/google.com/cloudsdktool/cloud-sdk\", \"pullTiming\": {\"endTime\": \"2026-07-16T14:53:18.148620260Z\", \"startTime\": \"2026-07-16T14:51:53.384613202Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-16T14:53:37.265153817Z\", \"startTime\": \"2026-07-16T14:51:53.384613202Z\"}, \"waitFor\": [\"push\"]}], \"substitutions\": {\"_ARTIFACT_REPO\": \"osvauco-repo\", \"_REGION\": \"us-central1\"}, \"timeout\": \"3600s\", \"timing\": {\"BUILD\": {\"endTime\": \"2026-07-16T14:53:37.265267217Z\", \"startTime\": \"2026-07-16T14:51:09.851362505Z\"}, \"FETCHSOURCE\": {\"endTime\": \"2026-07-16T14:51:09.850884471Z\", \"startTime\": \"2026-07-16T14:51:04.949344560Z\"}, \"STORAGE_SOURCE\": {\"endTime\": \"2026-07-16T14:51:09.850884471Z\", \"startTime\": \"2026-07-16T14:51:04.949344560Z\"}}}, {\"createTime\": \"2026-07-16T14:18:57.426110934Z\", \"finishTime\": \"2026-07-16T14:21:08.904775Z\", \"id\": \"94fdfbe0-e755-4845-9815-de895987dcde\", \"logUrl\": \"https://console.cloud.google.com/cloud-build/builds/94fdfbe0-e755-4845-9815-de895987dcde?project=357036551735\", \"logsBucket\": \"gs://357036551735.cloudbuild-logs.googleusercontent.com\", \"name\": \"projects/357036551735/locations/global/builds/94fdfbe0-e755-4845-9815-de895987dcde\", \"options\": {\"logging\": \"LEGACY\", \"pool\": {}}, \"projectId\": \"propane-will-491900-m5\", \"queueTtl\": \"3600s\", \"results\": {\"buildStepImages\": [\"sha256:1a2d12db4fd104729db11e4ab2422c4fcb81ea61c656f57451295b3ff3c77abd\", \"sha256:1a2d12db4fd104729db11e4ab2422c4fcb81ea61c656f57451295b3ff3c77abd\", \"sha256:934ae3cc409975d61148e7aa3705ac3676f65b8ff17ad883f731881d77c9b92d\"], \"buildStepOutputs\": [\"\", \"\", \"\"], \"buildStepResults\": {\"build\": {}, \"deploy\": {}, \"push\": {}}}, \"serviceAccount\": \"projects/propane-will-491900-m5/serviceAccounts/357036551735-compute@developer.gserviceaccount.com\", \"source\": {\"storageSource\": {\"bucket\": \"propane-will-491900-m5_cloudbuild\", \"generation\": \"1784211537197728\", \"object\": \"source/1784211536.58029-bdd834473c6c4c26ba7d88501bb9faad.tgz\"}}, \"sourceProvenance\": {\"fileHashes\": {\"gs://propane-will-491900-m5_cloudbuild/source/1784211536.58029-bdd834473c6c4c26ba7d88501bb9faad.tgz#1784211537197728\": {\"fileHash\": [{\"type\": \"MD5\", \"value\": \"-UFqGxQSl722iksMd6oSsw==\"}, {\"type\": \"SHA256\", \"value\": \"g7rrrDlRBds7tkYvV3S5Kn-4m5uqNB9IgWQxLAg_HD4=\"}]}}, \"resolvedStorageSource\": {\"bucket\": \"propane-will-491900-m5_cloudbuild\", \"generation\": \"1784211537197728\", \"object\": \"source/1784211536.58029-bdd834473c6c4c26ba7d88501bb9faad.tgz\"}}, \"startTime\": \"2026-07-16T14:18:58.382698344Z\", \"status\": \"SUCCESS\", \"steps\": [{\"args\": [\"build\", \"-f\", \"opax-mcp/Dockerfile\", \"-t\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:94fdfbe0-e755-4845-9815-de895987dcde\", \"opax-mcp\"], \"id\": \"build\", \"name\": \"gcr.io/cloud-builders/docker\", \"pullTiming\": {\"endTime\": \"2026-07-16T14:19:03.023961226Z\", \"startTime\": \"2026-07-16T14:19:03.017335096Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-16T14:19:27.537754465Z\", \"startTime\": \"2026-07-16T14:19:03.017335096Z\"}}, {\"args\": [\"push\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:94fdfbe0-e755-4845-9815-de895987dcde\"], \"id\": \"push\", \"name\": \"gcr.io/cloud-builders/docker\", \"pullTiming\": {\"endTime\": \"2026-07-16T14:19:27.539633443Z\", \"startTime\": \"2026-07-16T14:19:27.537891951Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-16T14:19:39.562587347Z\", \"startTime\": \"2026-07-16T14:19:27.537891951Z\"}, \"waitFor\": [\"build\"]}, {\"args\": [\"run\", \"deploy\", \"opax-mcp\", \"--image=us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:94fdfbe0-e755-4845-9815-de895987dcde\", \"--region=us-central1\"], \"entrypoint\": \"gcloud\", \"id\": \"deploy\", \"name\": \"gcr.io/google.com/cloudsdktool/cloud-sdk\", \"pullTiming\": {\"endTime\": \"2026-07-16T14:20:50.514146424Z\", \"startTime\": \"2026-07-16T14:19:39.562717888Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-16T14:21:07.542750564Z\", \"startTime\": \"2026-07-16T14:19:39.562717888Z\"}, \"waitFor\": [\"push\"]}], \"substitutions\": {\"_ARTIFACT_REPO\": \"osvauco-repo\", \"_REGION\": \"us-central1\"}, \"timeout\": \"3600s\", \"timing\": {\"BUILD\": {\"endTime\": \"2026-07-16T14:21:07.542859956Z\", \"startTime\": \"2026-07-16T14:19:02.370288960Z\"}, \"FETCHSOURCE\": {\"endTime\": \"2026-07-16T14:19:02.370165527Z\", \"startTime\": \"2026-07-16T14:18:59.367017701Z\"}, \"STORAGE_SOURCE\": {\"endTime\": \"2026-07-16T14:19:02.370165527Z\", \"startTime\": \"2026-07-16T14:18:59.367017701Z\"}}}, {\"createTime\": \"2026-07-16T12:53:50.954491311Z\", \"finishTime\": \"2026-07-16T12:55:53.028949Z\", \"id\": \"3480abaf-5001-4158-9ab1-204fc771b143\", \"logUrl\": \"https://console.cloud.google.com/cloud-build/builds/3480abaf-5001-4158-9ab1-204fc771b143?project=357036551735\", \"logsBucket\": \"gs://357036551735.cloudbuild-logs.googleusercontent.com\", \"name\": \"projects/357036551735/locations/global/builds/3480abaf-5001-4158-9ab1-204fc771b143\", \"options\": {\"logging\": \"LEGACY\", \"pool\": {}}, \"projectId\": \"propane-will-491900-m5\", \"queueTtl\": \"3600s\", \"results\": {\"buildStepImages\": [\"sha256:1a2d12db4fd104729db11e4ab2422c4fcb81ea61c656f57451295b3ff3c77abd\", \"sha256:1a2d12db4fd104729db11e4ab2422c4fcb81ea61c656f57451295b3ff3c77abd\", \"sha256:934ae3cc409975d61148e7aa3705ac3676f65b8ff17ad883f731881d77c9b92d\"], \"buildStepOutputs\": [\"\", \"\", \"\"], \"buildStepResults\": {\"build\": {}, \"deploy\": {}, \"push\": {}}}, \"serviceAccount\": \"projects/propane-will-491900-m5/serviceAccounts/357036551735-compute@developer.gserviceaccount.com\", \"source\": {\"storageSource\": {\"bucket\": \"propane-will-491900-m5_cloudbuild\", \"generation\": \"1784206430611911\", \"object\": \"source/1784206429.949218-8f66aeac8ba34f4fa6bcbdda952263c2.tgz\"}}, \"sourceProvenance\": {\"fileHashes\": {\"gs://propane-will-491900-m5_cloudbuild/source/1784206429.949218-8f66aeac8ba34f4fa6bcbdda952263c2.tgz#1784206430611911\": {\"fileHash\": [{\"type\": \"MD5\", \"value\": \"VMb0wye5W3-ih1-eC2iMdg==\"}, {\"type\": \"SHA256\", \"value\": \"oTkhuKoaWKzz8gvKOUKBbI-XPpepMafAPJyA3jW5i6Y=\"}]}}, \"resolvedStorageSource\": {\"bucket\": \"propane-will-491900-m5_cloudbuild\", \"generation\": \"1784206430611911\", \"object\": \"source/1784206429.949218-8f66aeac8ba34f4fa6bcbdda952263c2.tgz\"}}, \"startTime\": \"2026-07-16T12:53:51.825547417Z\", \"status\": \"SUCCESS\", \"steps\": [{\"args\": [\"build\", \"-f\", \"opax-mcp/Dockerfile\", \"-t\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:3480abaf-5001-4158-9ab1-204fc771b143\", \"opax-mcp\"], \"id\": \"build\", \"name\": \"gcr.io/cloud-builders/docker\", \"pullTiming\": {\"endTime\": \"2026-07-16T12:53:56.829052269Z\", \"startTime\": \"2026-07-16T12:53:56.824680305Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-16T12:54:18.212902171Z\", \"startTime\": \"2026-07-16T12:53:56.824680305Z\"}}, {\"args\": [\"push\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:3480abaf-5001-4158-9ab1-204fc771b143\"], \"id\": \"push\", \"name\": \"gcr.io/cloud-builders/docker\", \"pullTiming\": {\"endTime\": \"2026-07-16T12:54:18.214015891Z\", \"startTime\": \"2026-07-16T12:54:18.212982518Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-16T12:54:30.828310614Z\", \"startTime\": \"2026-07-16T12:54:18.212982518Z\"}, \"waitFor\": [\"build\"]}, {\"args\": [\"run\", \"deploy\", \"opax-mcp\", \"--image=us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:3480abaf-5001-4158-9ab1-204fc771b143\", \"--region=us-central1\"], \"entrypoint\": \"gcloud\", \"id\": \"deploy\", \"name\": \"gcr.io/google.com/cloudsdktool/cloud-sdk\", \"pullTiming\": {\"endTime\": \"2026-07-16T12:55:34.714739927Z\", \"startTime\": \"2026-07-16T12:54:30.828395230Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-16T12:55:52.040964045Z\", \"startTime\": \"2026-07-16T12:54:30.828395230Z\"}, \"waitFor\": [\"push\"]}], \"substitutions\": {\"_ARTIFACT_REPO\": \"osvauco-repo\", \"_REGION\": \"us-central1\"}, \"timeout\": \"3600s\", \"timing\": {\"BUILD\": {\"endTime\": \"2026-07-16T12:55:52.041036988Z\", \"startTime\": \"2026-07-16T12:53:56.264117797Z\"}, \"FETCHSOURCE\": {\"endTime\": \"2026-07-16T12:53:56.264042009Z\", \"startTime\": \"2026-07-16T12:53:53.836900215Z\"}, \"STORAGE_SOURCE\": {\"endTime\": \"2026-07-16T12:53:56.264042009Z\", \"startTime\": \"2026-07-16T12:53:53.836900215Z\"}}}, {\"createTime\": \"2026-07-16T12:30:21.755500496Z\", \"finishTime\": \"2026-07-16T12:32:43.168387Z\", \"id\": \"d5d6a2f2-c642-4ed0-8d4d-562d0ff088c9\", \"logUrl\": \"https://console.cloud.google.com/cloud-build/builds/d5d6a2f2-c642-4ed0-8d4d-562d0ff088c9?project=357036551735\", \"logsBucket\": \"gs://357036551735.cloudbuild-logs.googleusercontent.com\", \"name\": \"projects/357036551735/locations/global/builds/d5d6a2f2-c642-4ed0-8d4d-562d0ff088c9\", \"options\": {\"logging\": \"LEGACY\", \"pool\": {}}, \"projectId\": \"propane-will-491900-m5\", \"queueTtl\": \"3600s\", \"results\": {\"buildStepImages\": [\"sha256:1a2d12db4fd104729db11e4ab2422c4fcb81ea61c656f57451295b3ff3c77abd\", \"sha256:1a2d12db4fd104729db11e4ab2422c4fcb81ea61c656f57451295b3ff3c77abd\", \"sha256:934ae3cc409975d61148e7aa3705ac3676f65b8ff17ad883f731881d77c9b92d\"], \"buildStepOutputs\": [\"\", \"\", \"\"], \"buildStepResults\": {\"build\": {}, \"deploy\": {}, \"push\": {}}}, \"serviceAccount\": \"projects/propane-will-491900-m5/serviceAccounts/357036551735-compute@developer.gserviceaccount.com\", \"source\": {\"storageSource\": {\"bucket\": \"propane-will-491900-m5_cloudbuild\", \"generation\": \"1784205021544554\", \"object\": \"source/1784205020.955979-6e2a588d49d74e5486d71eda35c5ffc0.tgz\"}}, \"sourceProvenance\": {\"fileHashes\": {\"gs://propane-will-491900-m5_cloudbuild/source/1784205020.955979-6e2a588d49d74e5486d71eda35c5ffc0.tgz#1784205021544554\": {\"fileHash\": [{\"type\": \"MD5\", \"value\": \"-szI9Bv5JzG1Ddi3IDg-NQ==\"}, {\"type\": \"SHA256\", \"value\": \"nqStY4yTwRmmL4u4BVOYm_tli5eixWhvB3FPsvVcvEw=\"}]}}, \"resolvedStorageSource\": {\"bucket\": \"propane-will-491900-m5_cloudbuild\", \"generation\": \"1784205021544554\", \"object\": \"source/1784205020.955979-6e2a588d49d74e5486d71eda35c5ffc0.tgz\"}}, \"startTime\": \"2026-07-16T12:30:22.680839719Z\", \"status\": \"SUCCESS\", \"steps\": [{\"args\": [\"build\", \"-f\", \"opax-mcp/Dockerfile\", \"-t\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:d5d6a2f2-c642-4ed0-8d4d-562d0ff088c9\", \"opax-mcp\"], \"id\": \"build\", \"name\": \"gcr.io/cloud-builders/docker\", \"pullTiming\": {\"endTime\": \"2026-07-16T12:30:29.960564393Z\", \"startTime\": \"2026-07-16T12:30:29.954574869Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-16T12:30:54.248651205Z\", \"startTime\": \"2026-07-16T12:30:29.954574869Z\"}}, {\"args\": [\"push\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:d5d6a2f2-c642-4ed0-8d4d-562d0ff088c9\"], \"id\": \"push\", \"name\": \"gcr.io/cloud-builders/docker\", \"pullTiming\": {\"endTime\": \"2026-07-16T12:30:54.250387037Z\", \"startTime\": \"2026-07-16T12:30:54.248947317Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-16T12:31:07.002443023Z\", \"startTime\": \"2026-07-16T12:30:54.248947317Z\"}, \"waitFor\": [\"build\"]}, {\"args\": [\"run\", \"deploy\", \"opax-mcp\", \"--image=us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:d5d6a2f2-c642-4ed0-8d4d-562d0ff088c9\", \"--region=us-central1\"], \"entrypoint\": \"gcloud\", \"id\": \"deploy\", \"name\": \"gcr.io/google.com/cloudsdktool/cloud-sdk\", \"pullTiming\": {\"endTime\": \"2026-07-16T12:32:17.044318307Z\", \"startTime\": \"2026-07-16T12:31:07.002599680Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-16T12:32:41.679359015Z\", \"startTime\": \"2026-07-16T12:31:07.002599680Z\"}, \"waitFor\": [\"push\"]}], \"substitutions\": {\"_ARTIFACT_REPO\": \"osvauco-repo\", \"_REGION\": \"us-central1\"}, \"timeout\": \"3600s\", \"timing\": {\"BUILD\": {\"endTime\": \"2026-07-16T12:32:41.679523385Z\", \"startTime\": \"2026-07-16T12:30:29.313675032Z\"}, \"FETCHSOURCE\": {\"endTime\": \"2026-07-16T12:30:29.313468210Z\", \"startTime\": \"2026-07-16T12:30:26.242864854Z\"}, \"STORAGE_SOURCE\": {\"endTime\": \"2026-07-16T12:30:29.313468210Z\", \"startTime\": \"2026-07-16T12:30:26.242864854Z\"}}}, {\"createTime\": \"2026-07-16T12:16:54.110816159Z\", \"failureInfo\": {\"detail\": \"Build step failure: build step 2 \\\"gcr.io/google.com/cloudsdktool/cloud-sdk\\\" failed: step exited with non-zero status: 1\", \"type\": \"USER_BUILD_STEP\"}, \"finishTime\": \"2026-07-16T12:18:55.691632Z\", \"id\": \"fb2cbe23-5c1e-461b-921d-62938473e016\", \"logUrl\": \"https://console.cloud.google.com/cloud-build/builds/fb2cbe23-5c1e-461b-921d-62938473e016?project=357036551735\", \"logsBucket\": \"gs://357036551735.cloudbuild-logs.googleusercontent.com\", \"name\": \"projects/357036551735/locations/global/builds/fb2cbe23-5c1e-461b-921d-62938473e016\", \"options\": {\"logging\": \"LEGACY\", \"pool\": {}}, \"projectId\": \"propane-will-491900-m5\", \"queueTtl\": \"3600s\", \"results\": {\"buildStepImages\": [\"sha256:1a2d12db4fd104729db11e4ab2422c4fcb81ea61c656f57451295b3ff3c77abd\", \"sha256:1a2d12db4fd104729db11e4ab2422c4fcb81ea61c656f57451295b3ff3c77abd\", \"sha256:934ae3cc409975d61148e7aa3705ac3676f65b8ff17ad883f731881d77c9b92d\"], \"buildStepOutputs\": [\"\", \"\", \"\"], \"buildStepResults\": {\"build\": {}, \"deploy\": {}, \"push\": {}}}, \"serviceAccount\": \"projects/propane-will-491900-m5/serviceAccounts/357036551735-compute@developer.gserviceaccount.com\", \"source\": {\"storageSource\": {\"bucket\": \"propane-will-491900-m5_cloudbuild\", \"generation\": \"1784204213906784\", \"object\": \"source/1784204213.299529-a53f6432680f417187e778590130c5ee.tgz\"}}, \"sourceProvenance\": {\"fileHashes\": {\"gs://propane-will-491900-m5_cloudbuild/source/1784204213.299529-a53f6432680f417187e778590130c5ee.tgz#1784204213906784\": {\"fileHash\": [{\"type\": \"MD5\", \"value\": \"Y8DbEQkXL6LdQP6mUppaGQ==\"}, {\"type\": \"SHA256\", \"value\": \"1SMZ6bEAxlkdaWWFDK6DH3G7WHlzdldFH49wTTlzCzE=\"}]}}, \"resolvedStorageSource\": {\"bucket\": \"propane-will-491900-m5_cloudbuild\", \"generation\": \"1784204213906784\", \"object\": \"source/1784204213.299529-a53f6432680f417187e778590130c5ee.tgz\"}}, \"startTime\": \"2026-07-16T12:16:54.952709036Z\", \"status\": \"FAILURE\", \"steps\": [{\"args\": [\"build\", \"-f\", \"opax-mcp/Dockerfile\", \"-t\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:fb2cbe23-5c1e-461b-921d-62938473e016\", \"opax-mcp\"], \"id\": \"build\", \"name\": \"gcr.io/cloud-builders/docker\", \"pullTiming\": {\"endTime\": \"2026-07-16T12:17:00.668573329Z\", \"startTime\": \"2026-07-16T12:17:00.659094944Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-16T12:17:20.015814456Z\", \"startTime\": \"2026-07-16T12:17:00.659094944Z\"}}, {\"args\": [\"push\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:fb2cbe23-5c1e-461b-921d-62938473e016\"], \"id\": \"push\", \"name\": \"gcr.io/cloud-builders/docker\", \"pullTiming\": {\"endTime\": \"2026-07-16T12:17:20.016895062Z\", \"startTime\": \"2026-07-16T12:17:20.015886890Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-16T12:17:28.992922866Z\", \"startTime\": \"2026-07-16T12:17:20.015886890Z\"}, \"waitFor\": [\"build\"]}, {\"args\": [\"run\", \"deploy\", \"opax-mcp\", \"--image=us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:fb2cbe23-5c1e-461b-921d-62938473e016\", \"--region=us-central1\"], \"entrypoint\": \"gcloud\", \"exitCode\": 1, \"id\": \"deploy\", \"name\": \"gcr.io/google.com/cloudsdktool/cloud-sdk\", \"pullTiming\": {\"endTime\": \"2026-07-16T12:18:20.966470998Z\", \"startTime\": \"2026-07-16T12:17:28.992998552Z\"}, \"status\": \"FAILURE\", \"timing\": {\"endTime\": \"2026-07-16T12:18:44.103636957Z\", \"startTime\": \"2026-07-16T12:17:28.992998552Z\"}, \"waitFor\": [\"push\"]}], \"substitutions\": {\"_ARTIFACT_REPO\": \"osvauco-repo\", \"_REGION\": \"us-central1\"}, \"timeout\": \"3600s\", \"timing\": {\"BUILD\": {\"endTime\": \"2026-07-16T12:18:44.103762747Z\", \"startTime\": \"2026-07-16T12:17:00.149946501Z\"}, \"FETCHSOURCE\": {\"endTime\": \"2026-07-16T12:17:00.149865588Z\", \"startTime\": \"2026-07-16T12:16:57.919498565Z\"}, \"STORAGE_SOURCE\": {\"endTime\": \"2026-07-16T12:17:00.149865588Z\", \"startTime\": \"2026-07-16T12:16:57.919498565Z\"}}}, {\"createTime\": \"2026-07-16T11:57:16.055638041Z\", \"finishTime\": \"2026-07-16T11:59:26.268327Z\", \"id\": \"fc7c9eca-4ab4-4814-8c1a-da4a882d5358\", \"logUrl\": \"https://console.cloud.google.com/cloud-build/builds/fc7c9eca-4ab4-4814-8c1a-da4a882d5358?project=357036551735\", \"logsBucket\": \"gs://357036551735.cloudbuild-logs.googleusercontent.com\", \"name\": \"projects/357036551735/locations/global/builds/fc7c9eca-4ab4-4814-8c1a-da4a882d5358\", \"options\": {\"logging\": \"LEGACY\", \"pool\": {}}, \"projectId\": \"propane-will-491900-m5\", \"queueTtl\": \"3600s\", \"results\": {\"buildStepImages\": [\"sha256:1a2d12db4fd104729db11e4ab2422c4fcb81ea61c656f57451295b3ff3c77abd\", \"sha256:1a2d12db4fd104729db11e4ab2422c4fcb81ea61c656f57451295b3ff3c77abd\", \"sha256:934ae3cc409975d61148e7aa3705ac3676f65b8ff17ad883f731881d77c9b92d\"], \"buildStepOutputs\": [\"\", \"\", \"\"], \"buildStepResults\": {\"build\": {}, \"deploy\": {}, \"push\": {}}}, \"serviceAccount\": \"projects/propane-will-491900-m5/serviceAccounts/357036551735-compute@developer.gserviceaccount.com\", \"source\": {\"storageSource\": {\"bucket\": \"propane-will-491900-m5_cloudbuild\", \"generation\": \"1784203035842148\", \"object\": \"source/1784203035.276219-ab487f3c9681401193b62f90d62d6794.tgz\"}}, \"sourceProvenance\": {\"fileHashes\": {\"gs://propane-will-491900-m5_cloudbuild/source/1784203035.276219-ab487f3c9681401193b62f90d62d6794.tgz#1784203035842148\": {\"fileHash\": [{\"type\": \"MD5\", \"value\": \"z5cUHip4y1JnmGI7bZDsAA==\"}, {\"type\": \"SHA256\", \"value\": \"d5wdlaWVgFck04ZEkOG3gGkO3S0gpl77hClXXmqLZ9Q=\"}]}}, \"resolvedStorageSource\": {\"bucket\": \"propane-will-491900-m5_cloudbuild\", \"generation\": \"1784203035842148\", \"object\": \"source/1784203035.276219-ab487f3c9681401193b62f90d62d6794.tgz\"}}, \"startTime\": \"2026-07-16T11:57:16.878813663Z\", \"status\": \"SUCCESS\", \"steps\": [{\"args\": [\"build\", \"-f\", \"opax-mcp/Dockerfile\", \"-t\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:fc7c9eca-4ab4-4814-8c1a-da4a882d5358\", \"opax-mcp\"], \"id\": \"build\", \"name\": \"gcr.io/cloud-builders/docker\", \"pullTiming\": {\"endTime\": \"2026-07-16T11:57:22.880590776Z\", \"startTime\": \"2026-07-16T11:57:22.872814236Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-16T11:57:48.296493823Z\", \"startTime\": \"2026-07-16T11:57:22.872814236Z\"}}, {\"args\": [\"push\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:fc7c9eca-4ab4-4814-8c1a-da4a882d5358\"], \"id\": \"push\", \"name\": \"gcr.io/cloud-builders/docker\", \"pullTiming\": {\"endTime\": \"2026-07-16T11:57:48.298439385Z\", \"startTime\": \"2026-07-16T11:57:48.296588874Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-16T11:58:01.071011392Z\", \"startTime\": \"2026-07-16T11:57:48.296588874Z\"}, \"waitFor\": [\"build\"]}, {\"args\": [\"run\", \"deploy\", \"opax-mcp\", \"--image=us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:fc7c9eca-4ab4-4814-8c1a-da4a882d5358\", \"--region=us-central1\"], \"entrypoint\": \"gcloud\", \"id\": \"deploy\", \"name\": \"gcr.io/google.com/cloudsdktool/cloud-sdk\", \"pullTiming\": {\"endTime\": \"2026-07-16T11:59:09.339880601Z\", \"startTime\": \"2026-07-16T11:58:01.071175077Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-16T11:59:25.419737142Z\", \"startTime\": \"2026-07-16T11:58:01.071175077Z\"}, \"waitFor\": [\"push\"]}], \"substitutions\": {\"_ARTIFACT_REPO\": \"osvauco-repo\", \"_REGION\": \"us-central1\"}, \"timeout\": \"3600s\", \"timing\": {\"BUILD\": {\"endTime\": \"2026-07-16T11:59:25.419848312Z\", \"startTime\": \"2026-07-16T11:57:22.279472331Z\"}, \"FETCHSOURCE\": {\"endTime\": \"2026-07-16T11:57:22.279387574Z\", \"startTime\": \"2026-07-16T11:57:19.358325556Z\"}, \"STORAGE_SOURCE\": {\"endTime\": \"2026-07-16T11:57:22.279387574Z\", \"startTime\": \"2026-07-16T11:57:19.358325556Z\"}}}, {\"createTime\": \"2026-07-16T11:52:21.599654947Z\", \"finishTime\": \"2026-07-16T11:54:47.943715Z\", \"id\": \"0262a2e5-1d07-4638-acae-6bcca893bf64\", \"logUrl\": \"https://console.cloud.google.com/cloud-build/builds/0262a2e5-1d07-4638-acae-6bcca893bf64?project=357036551735\", \"logsBucket\": \"gs://357036551735.cloudbuild-logs.googleusercontent.com\", \"name\": \"projects/357036551735/locations/global/builds/0262a2e5-1d07-4638-acae-6bcca893bf64\", \"options\": {\"logging\": \"LEGACY\", \"pool\": {}}, \"projectId\": \"propane-will-491900-m5\", \"queueTtl\": \"3600s\", \"results\": {\"buildStepImages\": [\"sha256:1a2d12db4fd104729db11e4ab2422c4fcb81ea61c656f57451295b3ff3c77abd\", \"sha256:1a2d12db4fd104729db11e4ab2422c4fcb81ea61c656f57451295b3ff3c77abd\", \"sha256:934ae3cc409975d61148e7aa3705ac3676f65b8ff17ad883f731881d77c9b92d\"], \"buildStepOutputs\": [\"\", \"\", \"\"], \"buildStepResults\": {\"build\": {}, \"deploy\": {}, \"push\": {}}}, \"serviceAccount\": \"projects/propane-will-491900-m5/serviceAccounts/357036551735-compute@developer.gserviceaccount.com\", \"source\": {\"storageSource\": {\"bucket\": \"propane-will-491900-m5_cloudbuild\", \"generation\": \"1784202741378443\", \"object\": \"source/1784202740.777137-ea1dfa84351e426c8de576b7d370d04c.tgz\"}}, \"sourceProvenance\": {\"fileHashes\": {\"gs://propane-will-491900-m5_cloudbuild/source/1784202740.777137-ea1dfa84351e426c8de576b7d370d04c.tgz#1784202741378443\": {\"fileHash\": [{\"type\": \"MD5\", \"value\": \"WOwx5VaQTbBX9AmeP9zluA==\"}, {\"type\": \"SHA256\", \"value\": \"Z0x1Z1ZtEXWYqbxduGbfJi3GIbna8nfh18Lg9gyVrvs=\"}]}}, \"resolvedStorageSource\": {\"bucket\": \"propane-will-491900-m5_cloudbuild\", \"generation\": \"1784202741378443\", \"object\": \"source/1784202740.777137-ea1dfa84351e426c8de576b7d370d04c.tgz\"}}, \"startTime\": \"2026-07-16T11:52:22.392527493Z\", \"status\": \"SUCCESS\", \"steps\": [{\"args\": [\"build\", \"-f\", \"opax-mcp/Dockerfile\", \"-t\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:0262a2e5-1d07-4638-acae-6bcca893bf64\", \"opax-mcp\"], \"id\": \"build\", \"name\": \"gcr.io/cloud-builders/docker\", \"pullTiming\": {\"endTime\": \"2026-07-16T11:52:28.616093420Z\", \"startTime\": \"2026-07-16T11:52:28.608060370Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-16T11:52:55.319730430Z\", \"startTime\": \"2026-07-16T11:52:28.608060370Z\"}}, {\"args\": [\"push\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:0262a2e5-1d07-4638-acae-6bcca893bf64\"], \"id\": \"push\", \"name\": \"gcr.io/cloud-builders/docker\", \"pullTiming\": {\"endTime\": \"2026-07-16T11:52:55.322031323Z\", \"startTime\": \"2026-07-16T11:52:55.319895883Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-16T11:53:10.026177034Z\", \"startTime\": \"2026-07-16T11:52:55.319895883Z\"}, \"waitFor\": [\"build\"]}, {\"args\": [\"run\", \"deploy\", \"opax-mcp\", \"--image=us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:0262a2e5-1d07-4638-acae-6bcca893bf64\", \"--region=us-central1\"], \"entrypoint\": \"gcloud\", \"id\": \"deploy\", \"name\": \"gcr.io/google.com/cloudsdktool/cloud-sdk\", \"pullTiming\": {\"endTime\": \"2026-07-16T11:54:28.349508219Z\", \"startTime\": \"2026-07-16T11:53:10.026318511Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-16T11:54:47.167753654Z\", \"startTime\": \"2026-07-16T11:53:10.026318511Z\"}, \"waitFor\": [\"push\"]}], \"substitutions\": {\"_ARTIFACT_REPO\": \"osvauco-repo\", \"_REGION\": \"us-central1\"}, \"timeout\": \"3600s\", \"timing\": {\"BUILD\": {\"endTime\": \"2026-07-16T11:54:47.168000141Z\", \"startTime\": \"2026-07-16T11:52:27.847904211Z\"}, \"FETCHSOURCE\": {\"endTime\": \"2026-07-16T11:52:27.847755545Z\", \"startTime\": \"2026-07-16T11:52:24.383289695Z\"}, \"STORAGE_SOURCE\": {\"endTime\": \"2026-07-16T11:52:27.847755545Z\", \"startTime\": \"2026-07-16T11:52:24.383289695Z\"}}}, {\"createTime\": \"2026-07-16T11:44:03.391670865Z\", \"finishTime\": \"2026-07-16T11:46:30.692286Z\", \"id\": \"45ebf44a-3312-4b0f-8fac-4dc4765e4e77\", \"logUrl\": \"https://console.cloud.google.com/cloud-build/builds/45ebf44a-3312-4b0f-8fac-4dc4765e4e77?project=357036551735\", \"logsBucket\": \"gs://357036551735.cloudbuild-logs.googleusercontent.com\", \"name\": \"projects/357036551735/locations/global/builds/45ebf44a-3312-4b0f-8fac-4dc4765e4e77\", \"options\": {\"logging\": \"LEGACY\", \"pool\": {}}, \"projectId\": \"propane-will-491900-m5\", \"queueTtl\": \"3600s\", \"results\": {\"buildStepImages\": [\"sha256:1a2d12db4fd104729db11e4ab2422c4fcb81ea61c656f57451295b3ff3c77abd\", \"sha256:1a2d12db4fd104729db11e4ab2422c4fcb81ea61c656f57451295b3ff3c77abd\", \"sha256:934ae3cc409975d61148e7aa3705ac3676f65b8ff17ad883f731881d77c9b92d\"], \"buildStepOutputs\": [\"\", \"\", \"\"], \"buildStepResults\": {\"build\": {}, \"deploy\": {}, \"push\": {}}}, \"serviceAccount\": \"projects/propane-will-491900-m5/serviceAccounts/357036551735-compute@developer.gserviceaccount.com\", \"source\": {\"storageSource\": {\"bucket\": \"propane-will-491900-m5_cloudbuild\", \"generation\": \"1784202243182759\", \"object\": \"source/1784202242.520051-ed1c4effefd2418b91d88cfb89a1d49e.tgz\"}}, \"sourceProvenance\": {\"fileHashes\": {\"gs://propane-will-491900-m5_cloudbuild/source/1784202242.520051-ed1c4effefd2418b91d88cfb89a1d49e.tgz#1784202243182759\": {\"fileHash\": [{\"type\": \"MD5\", \"value\": \"POuuiGyVsH8DoHyN2enOBA==\"}, {\"type\": \"SHA256\", \"value\": \"4HmQDSYLc3nvRnHfHGnwhjtgEpQ-NzAysw90fuZgjeY=\"}]}}, \"resolvedStorageSource\": {\"bucket\": \"propane-will-491900-m5_cloudbuild\", \"generation\": \"1784202243182759\", \"object\": \"source/1784202242.520051-ed1c4effefd2418b91d88cfb89a1d49e.tgz\"}}, \"startTime\": \"2026-07-16T11:44:04.265098155Z\", \"status\": \"SUCCESS\", \"steps\": [{\"args\": [\"build\", \"-f\", \"opax-mcp/Dockerfile\", \"-t\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:45ebf44a-3312-4b0f-8fac-4dc4765e4e77\", \"opax-mcp\"], \"id\": \"build\", \"name\": \"gcr.io/cloud-builders/docker\", \"pullTiming\": {\"endTime\": \"2026-07-16T11:44:09.588385991Z\", \"startTime\": \"2026-07-16T11:44:09.582052002Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-16T11:44:34.870352509Z\", \"startTime\": \"2026-07-16T11:44:09.582052002Z\"}}, {\"args\": [\"push\", \"us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:45ebf44a-3312-4b0f-8fac-4dc4765e4e77\"], \"id\": \"push\", \"name\": \"gcr.io/cloud-builders/docker\", \"pullTiming\": {\"endTime\": \"2026-07-16T11:44:34.871903295Z\", \"startTime\": \"2026-07-16T11:44:34.870442797Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-16T11:44:49.610334186Z\", \"startTime\": \"2026-07-16T11:44:34.870442797Z\"}, \"waitFor\": [\"build\"]}, {\"args\": [\"run\", \"deploy\", \"opax-mcp\", \"--image=us-central1-docker.pkg.dev/propane-will-491900-m5/osvauco-repo/opax-mcp:45ebf44a-3312-4b0f-8fac-4dc4765e4e77\", \"--region=us-central1\"], \"entrypoint\": \"gcloud\", \"id\": \"deploy\", \"name\": \"gcr.io/google.com/cloudsdktool/cloud-sdk\", \"pullTiming\": {\"endTime\": \"2026-07-16T11:45:57.014509825Z\", \"startTime\": \"2026-07-16T11:44:49.610422267Z\"}, \"status\": \"SUCCESS\", \"timing\": {\"endTime\": \"2026-07-16T11:46:30.069671271Z\", \"startTime\": \"2026-07-16T11:44:49.610422267Z\"}, \"waitFor\": [\"push\"]}], \"substitutions\": {\"_ARTIFACT_REPO\": \"osvauco-repo\", \"_REGION\": \"us-central1\"}, \"timeout\": \"3600s\", \"timing\": {\"BUILD\": {\"endTime\": \"2026-07-16T11:46:30.069817350Z\", \"startTime\": \"2026-07-16T11:44:08.961705205Z\"}, \"FETCHSOURCE\": {\"endTime\": \"2026-07-16T11:44:08.961577858Z\", \"startTime\": \"2026-07-16T11:44:06.192565187Z\"}, \"STORAGE_SOURCE\": {\"endTime\": \"2026-07-16T11:44:08.961577858Z\", \"startTime\": \"2026-07-16T11:44:06.192565187Z\"}}}]}"}]}} - -=== list_workspace_users === -{"jsonrpc":"2.0","id":1,"result":{"meta":{"status":"Vellykket.","next_action":"Se over data og fortsett.","choices":[{"id":"continue","label":"Fortsett","style":"primary"}]},"content":[{"type":"text","text":"{\"users\": [{\"email\": \"chris.christiansen@vauco.no\", \"name\": \"Chris Christiansen\"}, {\"email\": \"marte.hein@vauco.no\", \"name\": \"Marte Hein\"}, {\"email\": \"pkchrist@vauco.no\", \"name\": \"Per Knut Christiansen\"}]}"}]}} - -=== list_emma_models === -{"jsonrpc":"2.0","id":1,"error":{"code":-32000,"message":""}} - -=== list_commits === -{"jsonrpc":"2.0","id":1,"error":{"code":-32000,"message":"Illegal header value b'token '"}} - diff --git a/toolcheck-readonly-params-20260721-114222.txt b/toolcheck-readonly-params-20260721-114222.txt deleted file mode 100644 index cdbe770..0000000 --- a/toolcheck-readonly-params-20260721-114222.txt +++ /dev/null @@ -1,10 +0,0 @@ -describe_service OK -list_service_revisions OK -get_cloud_run_logs OK -get_build_log OK -get_workspace_user OK -get_emails OK -list_calendar_events OK -get_file FAIL :: Illegal header value b'token ' -get_file_content FAIL :: GITEA_URL environment variable is not set. -list_repo_files FAIL :: GITEA_URL environment variable is not set. diff --git a/toolcheck-summary-20260721-113909.txt b/toolcheck-summary-20260721-113909.txt deleted file mode 100644 index 53cb9c2..0000000 --- a/toolcheck-summary-20260721-113909.txt +++ /dev/null @@ -1,11 +0,0 @@ -get_connector_status OK -get_billing_summary OK -get_health OK -get_build_status OK -get_state OK -list_customers OK -list_gce_instances OK -list_builds OK -list_workspace_users OK -list_emma_models FAIL :: -list_commits FAIL :: Illegal header value b'token '