Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -76,5 +76,8 @@ CLOUD_AI_MODEL_BACKEND=yoloe
# Weights: yoloe-11s-seg.pt (yoloe) | yolo26s.pt / yolov8n.pt (yolo26) | rfdetr-base (rfdetr)
CLOUD_AI_MODEL=yoloe-11s-seg.pt
CLOUD_AI_DEVICE=cpu
# Optional YOLOE prompt->cue overrides, e.g. "open bag=open_bag,item in hand=product_in_hand"
# Optional YOLOE prompt->cue overrides (comma-separated prompt=cue pairs).
# Leave blank to use built-in defaults (12 prompts in cloud-ai/app/detector.py).
# Production maps jacket phrases to concealment; older eval JSON used open_bag — see docs/AZURE_MVP_DEPLOY.md.
# CLOUD_AI_YOLOE_PROMPTS=person=person,open bag=open_bag,person hiding item inside jacket=concealment
CLOUD_AI_YOLOE_PROMPTS=
20 changes: 12 additions & 8 deletions Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ pipeline {

environment {
VM_APP_DIR = '/opt/onevo/app'
ONEVO_PYTHON = 'C:\\Users\\Abdul Baasith\\AppData\\Local\\Python\\bin\\python.exe'
ONEVO_ISCC = 'C:\\Users\\Abdul Baasith\\AppData\\Local\\Programs\\Inno Setup 6\\ISCC.exe'
Comment on lines +15 to +16

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Move tool paths out of the pipeline source. agent any can run under a different node or service account, where C:\Users\Abdul Baasith\... does not exist; the current documentation also makes operators edit versioned pipeline code for machine configuration.

  • Jenkinsfile#L15-L16: remove the profile-specific values and obtain ONEVO_PYTHON/ONEVO_ISCC from node-level Jenkins environment configuration.
  • docs/JENKINS_DEPLOY.md#L15-L16: instruct operators to configure both variables on the Jenkins agent/service account, not in Jenkinsfile.
  • docs/JENKINS_DEPLOY.md#L94-L94: include Python-path configuration alongside ISCC troubleshooting.
📍 Affects 2 files
  • Jenkinsfile#L15-L16 (this comment)
  • docs/JENKINS_DEPLOY.md#L15-L16
  • docs/JENKINS_DEPLOY.md#L94-L94
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Jenkinsfile` around lines 15 - 16, Move the profile-specific ONEVO_PYTHON and
ONEVO_ISCC assignments out of Jenkinsfile and rely on node-level Jenkins
environment configuration. In docs/JENKINS_DEPLOY.md lines 15-16, instruct
operators to configure both variables for the Jenkins agent/service account
rather than editing Jenkinsfile; in line 94, include Python-path troubleshooting
alongside ISCC troubleshooting.

}

stages {
Expand All @@ -37,8 +39,8 @@ pipeline {
stage('Connector tests') {
steps {
dir('connector') {
bat '"C:\\Users\\Abdul Baasith\\AppData\\Local\\Python\\bin\\python.exe" -m pip install -r requirements.txt pytest'
bat 'set PYTHONPATH=.&& "C:\\Users\\Abdul Baasith\\AppData\\Local\\Python\\bin\\python.exe" -m pytest tests/ -q'
bat '"%ONEVO_PYTHON%" -m pip install -r requirements.txt pytest'
bat 'set PYTHONPATH=.&& "%ONEVO_PYTHON%" -m pytest tests/ -q'
}
}
}
Expand All @@ -56,13 +58,15 @@ pipeline {
keyFileVariable: 'SSH_KEY',
usernameVariable: 'SSH_USER'
)]) {
bat """
bat '''
powershell -ExecutionPolicy Bypass -File scripts/deploy-vm.ps1 ^
-VmHost ${params.VM_HOST} ^
-VmUser ${params.VM_USER} ^
-BackendUrl ${params.BACKEND_URL} ^
-SshKeyPath "${env.SSH_KEY}"${extra}
"""
-VmHost ''' + params.VM_HOST + ''' ^
-VmUser ''' + params.VM_USER + ''' ^
-BackendUrl ''' + params.BACKEND_URL + ''' ^
-PythonPath "%ONEVO_PYTHON%" ^
-IsccPath "%ONEVO_ISCC%" ^
-SshKeyPath "%SSH_KEY%"''' + extra + '''
'''
}
}
}
Expand Down
18 changes: 16 additions & 2 deletions cloud-ai/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

from .backend_client import BackendClient
from .config import Config
from .detector import DetectorBackend, build_detector
from .detector import DEFAULT_YOLOE_PROMPTS, DetectorBackend, build_detector
from .events import extract_events
from .reid import ReIDExtractor
from .s3 import ClipStore
Expand Down Expand Up @@ -53,12 +53,23 @@ def process_job(job: dict, cfg: Config, store: ClipStore, detector: DetectorBack
def main() -> None:
cfg = Config.load()
host, _, port = cfg.redis_connection.partition(":")
r = redis.Redis(host=host, port=int(port or 6379), decode_responses=True)
r = redis.Redis(
host=host,
port=int(port or 6379),
decode_responses=True,
socket_timeout=None,
socket_connect_timeout=5,
)
Comment on lines +56 to +62

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files:"
git ls-files | sed -n '1,120p'

echo
echo "cloud-ai/app/main.py outline:"
ast-grep outline cloud-ai/app/main.py || true

echo
echo "cloud-ai/app/main.py relevant lines:"
cat -n cloud-ai/app/main.py | sed -n '1,130p'

echo
echo "docs AZURE_MVP_DEPLOY relevant lines:"
cat -n docs/AZURE_MVP_DEPLOY.md | sed -n '140,180p'

echo
echo "Search Redis socket_timeout and BRPOP:"
rg -n "socket_timeout|BRPOP|decode_responses|redis\.Redis|Timeout reading from socket|worker" cloud-ai app docs .env.example infra -S || true

Repository: baasith6/camera-phase-1

Length of output: 13886


🌐 Web query:

python-redis socket_timeout None blocking command socket_timeout None BRPOP command documentation

💡 Result:

In redis-py, the interaction between the client's socket_timeout and blocking commands like BRPOP has evolved significantly to resolve historical issues where socket-level timeouts would prematurely terminate blocking operations [1][2][3]. 1. Understanding the Timeouts - socket_timeout: This is a client-level configuration that sets a timeout for all low-level socket read/write operations [4][5]. If set to None (the default), the client waits indefinitely for a response from the Redis server [4]. - BRPOP timeout: This is an application-level parameter sent to the Redis server, which determines how long the server should wait for an element to become available in the specified list(s) [6]. A timeout of 0 instructs the server to block indefinitely [6]. 2. Historical Behavior and Issues Previously, if a user set a socket_timeout that was shorter than the BRPOP command's timeout, the client would raise a redis.exceptions.TimeoutError when the socket read exceeded the socket_timeout, even if the Redis server was still waiting for data [1][7]. Users often had to manually work around this by either using a separate Redis client instance without a socket_timeout for blocking operations or by setting the socket_timeout to be longer than the intended blocking duration [7][8]. 3. Current Behavior Recent updates to redis-py have addressed this by introducing logic to identify blocking commands [2][3]. When a blocking command (e.g., BRPOP, BLPOP, BZMPOP) is executed, the client now effectively bypasses or manages the socket_timeout for that specific operation [2][3]. Specifically, the client passes an internal indicator to the connection's read response method, which allows the socket to wait for the duration specified by the server (or indefinitely if the command timeout is 0) without being prematurely cut off by the client's global socket_timeout [2][3]. This ensures that the Redis server maintains control over the blocking duration, as intended [2]. Best Practice While the library now handles this automatically, it is still strongly recommended to set a non-None socket_timeout for production clients to prevent applications from hanging indefinitely in the event of network partitions or dead connections [4]. If your application requires robust blocking behavior, ensure you are using a current version of redis-py to take advantage of these improvements [2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "redis dependency declarations:"
rg -n "redis\s*[:=][<>=~^>]*|redis" cloud-ai/requirements*.txt pyproject.toml poetry.lock Pipfile.lock cloud-ai/Dockerfile -S || true

echo
echo "redis-py changelog/docs availability in repo:"
fd -i 'redis|requirements|pyproject|poetry.lock|lock|Dockerfile|rfdetr|pip' cloud-ai -t f -x sh -c 'echo "--- $1"; sed -n "1,160p" "$1"' sh {}

Repository: baasith6/camera-phase-1

Length of output: 1463


Keep Redis reads bounded and align the troubleshooting guidance.

socket_timeout=None allows stalled Redis reads to block indefinitely, while BRPOP still only controls Redis server-side waiting. Use a finite client socket timeout plus reconnect/retry handling, and update the troubleshooting doc so it no longer presents socket_timeout=None as the fix.

  • cloud-ai/app/main.py#L56-L62: replace the unbounded read timeout with a finite timeout and retain explicit timeout recovery.
  • docs/AZURE_MVP_DEPLOY.md#L165-L165: document the finite-timeout/reconnect strategy instead of Redis socket_timeout=None.
📍 Affects 2 files
  • cloud-ai/app/main.py#L56-L62 (this comment)
  • docs/AZURE_MVP_DEPLOY.md#L165-L165
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cloud-ai/app/main.py` around lines 56 - 62, Update the Redis client
construction in cloud-ai/app/main.py lines 56-62 to use a finite socket_timeout
and preserve explicit timeout recovery with reconnect/retry handling. Update
docs/AZURE_MVP_DEPLOY.md line 165 to describe the finite-timeout and reconnect
strategy instead of recommending Redis socket_timeout=None.

store = ClipStore(cfg)
backend = BackendClient(cfg.backend_url, cfg.service_key)

print(f"[cloud-ai] loading backend={cfg.model_backend} model={cfg.model} device={cfg.device} ...", flush=True)
detector = build_detector(cfg.model_backend, cfg.model, cfg.device, cfg.yoloe_prompts)
active_prompts = cfg.yoloe_prompts or DEFAULT_YOLOE_PROMPTS
print(
f"[cloud-ai] YOLOE prompts ({len(active_prompts)}): {', '.join(active_prompts.keys())}",
Comment on lines 67 to +70

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Align prompt override semantics across runtime and deployment documentation.

A non-empty CLOUD_AI_YOLOE_PROMPTS mapping currently replaces the entire built-in prompt map, while the examples imply partial overrides. This can silently reduce detection coverage from 12 prompts to only the entries supplied.

  • cloud-ai/app/main.py#L67-L70: merge configured entries over DEFAULT_YOLOE_PROMPTS before building and logging the detector, or validate a complete map.
  • .env.example#L79-L82: remove the partial three-entry example or explicitly require all built-in prompts.
  • infra/mvp/.env.production.example#L61-L64: apply the same complete-map wording and example correction.
  • docs/AZURE_MVP_DEPLOY.md#L166-L166: explain that current values replace defaults, or document the merged behavior after fixing runtime normalization.
📍 Affects 4 files
  • cloud-ai/app/main.py#L67-L70 (this comment)
  • .env.example#L79-L82
  • infra/mvp/.env.production.example#L61-L64
  • docs/AZURE_MVP_DEPLOY.md#L166-L166
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cloud-ai/app/main.py` around lines 67 - 70, Normalize YOLOE prompt
configuration in the detector initialization flow so configured entries merge
over DEFAULT_YOLOE_PROMPTS before building and logging the detector, preserving
all unspecified defaults. Update .env.example lines 79-82 and
infra/mvp/.env.production.example lines 61-64 to show or require complete-map
semantics, and update docs/AZURE_MVP_DEPLOY.md line 166 to document the merged
behavior.

flush=True,
)
reid_extractor = ReIDExtractor(device=cfg.device)
print("[cloud-ai] ready, waiting for clip jobs", flush=True)

Expand Down Expand Up @@ -94,6 +105,9 @@ def main() -> None:
print(f"[cloud-ai] clip {job.get('clipId')} dead-lettered after "
f"{MAX_RETRIES} attempts", flush=True)

except redis.TimeoutError:
# BRPOP idle timeout with socket_timeout=None should return None; ignore stray timeouts.
continue
Comment on lines +108 to +110

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== candidate files =="
git ls-files | rg '(^|/)main\.py$|cloud-ai/app/main\.py' || true

echo "== file outline/size =="
wc -l cloud-ai/app/main.py
ast-grep outline cloud-ai/app/main.py --view expanded || true

echo "== relevant section =="
sed -n '70,125p' cloud-ai/app/main.py

echo "== redis/lpush occurrences =="
rg -n "TimeoutError|brpop|lpush|continue|retry|dead" cloud-ai/app/main.py

Repository: baasith6/camera-phase-1

Length of output: 3584


🌐 Web query:

Redis Redis-py TimeoutOperationError documentation operation failure retry backoff

💡 Result:

In the redis-py library, there is no exception named TimeoutOperationError. The standard exception for operation timeouts is redis.exceptions.TimeoutError [1][2]. Redis-py includes built-in, configurable support for retrying operations that fail due to transient errors, such as TimeoutError and ConnectionError [3][4]. Retry and Backoff Configuration: You can configure retry behavior by passing a Retry object to your Redis client instance [3][5]. This object allows you to specify a backoff strategy and the maximum number of retry attempts [6][3]. 1. Retry Instance: Defines how many times to retry and which exceptions trigger a retry [3]. 2. Backoff Strategy: Controls the delay between retry attempts. Common strategies include ExponentialBackoff, ExponentialWithJitterBackoff, and ConstantBackoff [7]. Using jitter (e.g., ExponentialWithJitterBackoff) is recommended to prevent thundering herd problems [3][7]. 3. Supported Errors: The Retry instance includes a supported_errors parameter to specify which exceptions should trigger a retry. By default, this typically includes ConnectionError and TimeoutError [6][3]. Example Configuration: Below is an example of how to configure a client with retry logic and exponential backoff: from redis import Redis from redis.retry import Retry from redis.backoff import ExponentialBackoff from redis.exceptions import ConnectionError, TimeoutError # Configure retry: 3 attempts, exponential backoff starting at 1 second, capped at 10 seconds retry_strategy = Retry(ExponentialBackoff(cap=10, base=1), 3) client = Redis( host='localhost', port=6379, retry=retry_strategy, retry_on_error=[ConnectionError, TimeoutError]) Default Behavior: As of recent versions (6.0.0+), redis-py automatically attempts a failed command three times by default using an exponential backoff strategy with jitter if no explicit retry configuration is provided [3]. Important Considerations: - Timeouts: Ensure your socket_timeout and socket_connect_timeout are set to appropriate values. Timeouts that are too short may trigger unnecessary retries for commands that would have succeeded, while excessively long timeouts can cause application threads to hang [3]. - Pipelines: Be aware that historical issues have been reported where retry logic did not behave as expected during pipeline execution [8]. Always verify behavior in your specific environment if using pipelines.

Citations:


Scope the timeout handler to BRPOP only.

The outer except redis.TimeoutError also catches timeouts from p.lpush(...) after a job has been dequeued and processed. In that case continue forgets the current job, so missed retry/dead-lettering can leave work lossy. Catch timeout only around brpop, or persist/requeue the affected job before continuing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cloud-ai/app/main.py` around lines 108 - 110, Restrict the redis.TimeoutError
handling in the worker loop to the BRPOP call only, so timeouts from p.lpush
after processing a dequeued job are not swallowed by the same continue path.
Preserve the existing idle-timeout behavior for BRPOP while ensuring
post-processing failures retain the current job for the established retry or
dead-letter flow.

except Exception as e: # noqa: BLE001
# Worker-level error (Redis, etc.) — don't lose the current job.
print(f"[cloud-ai] worker error: {e}", flush=True)
Expand Down
24 changes: 5 additions & 19 deletions cloud-ai/eval/run_jacket_test.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
"""One-off: re-evaluate the 5 local clips with jacket-concealment-aware YOLOE prompts.
"""Re-evaluate local clips using production DEFAULT_YOLOE_PROMPTS (same as Azure cloud-ai).

The stock prompt set only covers bag-based concealment (open bag / backpack). All four
theft clips here conceal items inside a jacket, so we extend the prompt set with
jacket/clothing concealment phrases mapped onto the existing canonical cues:
- concealment-in-clothing phrases -> open_bag (drives BagOpen + Concealment)
- extra holding phrases -> product_in_hand (drives RepeatedHandling/Concealment)
Jacket/clothing phrases map to the concealment cue (not open_bag). Matches
cloud-ai/app/detector.py and the deployed Azure cloud-ai container.

Usage: cd cloud-ai && python -m eval.run_jacket_test
"""
Expand All @@ -18,17 +15,6 @@
from app.zones import Zone
from eval.run_eval import score_events

JACKET_PROMPTS = {
**DEFAULT_YOLOE_PROMPTS,
# Clothing-concealment cues (proxy onto open_bag so Concealment/BagOpen logic fires).
"person hiding item inside jacket": "open_bag",
"person putting object under clothing": "open_bag",
"hand inside jacket": "open_bag",
# Extra holding phrasings for small items / clothing items.
"person holding a bottle": "product_in_hand",
"person holding clothes": "product_in_hand",
}


def main() -> int:
base_dir = os.path.dirname(__file__)
Expand All @@ -40,7 +26,7 @@ def main() -> int:
default_zone = Zone(id="default", name=dz["name"], zone_type=dz["zoneType"],
polygon=[(float(x), float(y)) for x, y in dz["polygon"]])

detector = build_detector("yoloe", "yoloe-11s-seg.pt", "cpu", yoloe_prompts=JACKET_PROMPTS)
detector = build_detector("yoloe", "yoloe-11s-seg.pt", "cpu", yoloe_prompts=DEFAULT_YOLOE_PROMPTS)

per_clip = []
for clip in gt["clips"]:
Expand All @@ -63,7 +49,7 @@ def main() -> int:

out = os.path.join(base_dir, "results_jacket_prompts.json")
with open(out, "w", encoding="utf-8") as f:
json.dump({"prompts": JACKET_PROMPTS, "alert_threshold": threshold,
json.dump({"prompts": DEFAULT_YOLOE_PROMPTS, "alert_threshold": threshold,
"per_clip": per_clip}, f, indent=2)
print(f"[jacket-test] wrote {out}")
return 0
Expand Down
2 changes: 1 addition & 1 deletion connector/app/baked_config.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# AUTO-GENERATED by installer/build.ps1 - do not edit by hand.
BAKED_BACKEND_URL = "http://localhost:8081"
BAKED_BACKEND_URL = "http://20.193.69.220:8081"
BAKED_AT_BUILD = True
64 changes: 55 additions & 9 deletions connector/installer/build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
param(
[Parameter(Mandatory = $true)]
[string]$BackendUrl,
[string]$PythonPath = "",
[string]$IsccPath = "",
[switch]$AllowHttp
)

Expand All @@ -23,17 +25,58 @@ function Assert-BackendUrl([string]$Url, [bool]$AllowHttp) {
throw "Production requires HTTPS (or http://localhost / http://127.0.0.1 for local builds). Got: $Url. Use -AllowHttp for MVP pilot without TLS."
}

function Find-ISCC {
function Find-ISCC([string]$ExplicitPath) {
$candidates = @(
$ExplicitPath,
$env:ONEVO_ISCC,
"${env:ProgramFiles(x86)}\Inno Setup 6\ISCC.exe",
"${env:ProgramFiles}\Inno Setup 6\ISCC.exe",
"$env:LOCALAPPDATA\Programs\Inno Setup 6\ISCC.exe",
(Get-Command ISCC -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source)
) | Where-Object { $_ -and (Test-Path $_) }
if (-not $candidates) {
throw "Inno Setup 6 (ISCC.exe) was not found. Install from https://jrsoftware.org/isdl.php"
)
$userRoots = Get-ChildItem 'C:\Users' -Directory -ErrorAction SilentlyContinue |
Where-Object { $_.Name -notin @('Public', 'Default', 'Default User', 'All Users') }
foreach ($user in $userRoots) {
$candidates += @(
(Join-Path $user.FullName 'AppData\Local\Programs\Inno Setup 6\ISCC.exe'),
(Join-Path $user.FullName 'AppData\Local\Programs\Inno Setup 6\Compil32.exe')
)
Comment on lines +40 to +43

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | grep -E '(^|/)build\.ps1$|Connector' | head -100

echo "== target =="
if [ -f connector/installer/build.ps1 ]; then
  cat -n connector/installer/build.ps1
else
  fd -a 'build\.ps1$' . | sed -n '1,80p'
fi

echo "== search iscc usage =="
rg -n "Compil32|ISCC|& \$iscc|\.(\$iscc)|Invoke|Start" connector/installer 2>/dev/null || true

Repository: baasith6/camera-phase-1

Length of output: 9382


🌐 Web query:

Inno Setup Compil32.exe ISCC.exe command line compiler GUI compiler

💡 Result:

Inno Setup provides two primary executables for compiling installation scripts: Compil32.exe and ISCC.exe. Both rely on the same core compilation engine (ISCmplr.dll) to generate installers [1]. Compil32.exe Compil32.exe is the GUI-based Integrated Development Environment (IDE) for Inno Setup [1]. It is primarily designed for developers to interactively write, edit, and compile installation scripts (.iss files) [1]. While it can be invoked from the command line using the /cc parameter (e.g., compil32 /cc "script.iss"), it is not intended for headless or silent automation, as it does not suppress the progress display or graphical error messages [2][3]. ISCC.exe ISCC.exe (Inno Setup Command-line Compiler) is a dedicated console-mode application specifically designed for command-line usage [2][4]. It is the recommended tool for build automation, continuous integration pipelines, and any scenario requiring silent or non-interactive compilation [5][4]. Key differences and features: - Usage: Use ISCC.exe for scripts, batch files, and automated build environments [5][4]. Use Compil32.exe for manual script development and editing [1]. - Silent Compilation: ISCC.exe supports quiet mode via the /Q or /Qp flags, allowing you to hide progress displays or suppress output except for error messages [2][4]. - Command-line Options: ISCC.exe accepts various parameters to override script directives at runtime, such as /O (output path), /F (output filename), and /S (SignTool configuration) [2][4]. It also supports extended parameters for the Inno Setup Preprocessor (ISPP) [6]. - Exit Codes: Both compilers return standard exit codes: 0 for success, 1 for invalid parameters/internal errors, and 2 for compilation failure [2][3]. For command-line automation, the standard practice is to use ISCC.exe [5][7]. You can verify available options by running iscc /? in your command prompt [5][4].

Citations:


Do not select Compil32.exe as an ISCC replacement.

The build invokes the selected compiler synchronously (& $iscc $IssFile). Compil32.exe is the GUI IDE and is not suitable for headless automation, so line 42 should be restricted to ISCC.exe.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@connector/installer/build.ps1` around lines 40 - 43, Update the candidate
list in the Inno Setup discovery logic to include only ISCC.exe and remove the
Compil32.exe path from the candidates added for each user. Preserve the existing
Join-Path and synchronous compiler selection flow.

}
return ($candidates | Select-Object -First 1)
$found = $candidates | Where-Object { $_ -and (Test-Path $_) } | Select-Object -First 1
if (-not $found) {
throw "Inno Setup 6 (ISCC.exe) was not found. Install from https://jrsoftware.org/isdl.php or set ONEVO_ISCC."
}
return $found
}

function Find-Python([string]$ExplicitPath) {
$candidates = @(
$ExplicitPath,
$env:ONEVO_PYTHON,
$env:PYTHON_EXE,
(Get-Command python -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source),
"$env:LOCALAPPDATA\Python\bin\python.exe",
"$env:LOCALAPPDATA\Programs\Python\Python314\python.exe",
"$env:LOCALAPPDATA\Programs\Python\Python313\python.exe",
"$env:LOCALAPPDATA\Programs\Python\Python312\python.exe",
"$env:LOCALAPPDATA\Programs\Python\Python311\python.exe"
)
$userRoots = Get-ChildItem 'C:\Users' -Directory -ErrorAction SilentlyContinue |
Where-Object { $_.Name -notin @('Public', 'Default', 'Default User', 'All Users') }
foreach ($user in $userRoots) {
$candidates += @(
(Join-Path $user.FullName 'AppData\Local\Python\bin\python.exe'),
(Join-Path $user.FullName 'AppData\Local\Programs\Python\Python314\python.exe'),
(Join-Path $user.FullName 'AppData\Local\Programs\Python\Python313\python.exe'),
(Join-Path $user.FullName 'AppData\Local\Programs\Python\Python312\python.exe'),
(Join-Path $user.FullName 'AppData\Local\Programs\Python\Python311\python.exe')
)
}
$found = $candidates | Where-Object { $_ -and (Test-Path $_) } | Select-Object -First 1
if (-not $found) {
throw "Python was not found. Set ONEVO_PYTHON or install Python 3.11+ and add it to PATH."
}
return $found
}

$BackendUrl = Assert-BackendUrl $BackendUrl $AllowHttp.IsPresent
Expand All @@ -51,8 +94,8 @@ try {
throw "Invalid WinSW XML ($WinSwConfig): $($_.Exception.Message)"
}

$py = Get-Command python -ErrorAction SilentlyContinue
if (-not $py) { throw "Python was not found on PATH" }
$python = Find-Python $PythonPath
Write-Host "==> Python: $python"

$baked = @"
# AUTO-GENERATED by installer/build.ps1 - do not edit by hand.
Expand All @@ -66,10 +109,13 @@ New-Item -ItemType Directory -Force -Path $DistDir | Out-Null
$pyinstallerOut = Join-Path $DistDir "onevo-connector.exe"
if (Test-Path $pyinstallerOut) { Remove-Item $pyinstallerOut -Force }

Write-Host "==> PyInstaller deps..."
& $python -m pip install -q -r (Join-Path $ConnectorRoot "requirements.txt") -r (Join-Path $ConnectorRoot "requirements-build.txt")

Write-Host "==> PyInstaller..."
Push-Location $ConnectorRoot
try {
& python -m PyInstaller --noconfirm --clean --distpath $DistDir --workpath (Join-Path $DistDir "build") $SpecFile
& $python -m PyInstaller --noconfirm --clean --distpath $DistDir --workpath (Join-Path $DistDir "build") $SpecFile
Comment on lines +112 to +118

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file exists and relevant lines =="
if [ -f connector/installer/build.ps1 ]; then
  nl -ba connector/installer/build.ps1 | sed -n '90,135p'
else
  echo "connector/installer/build.ps1 not found"
fi

echo
echo "== search for PowerShell exit code handling in same file =="
rg -n 'LASTEXITCODE|exit|pip|PyInstaller|Set-StrictMode|ErrorAction' connector/installer/build.ps1 || true

echo
echo "== package manifest snippets if present =="
git ls-files | rg '(^|/)requirements.*\.txt$|package\.json|pyproject\.toml|Pipfile|poetry\.lock' || true

Repository: baasith6/camera-phase-1

Length of output: 238


🏁 Script executed:

#!/bin/sh
set -euo pipefail

echo "== file metadata =="
test -f connector/installer/build.ps1 && echo "found" || echo "missing"

echo
echo "== relevant lines using awk =="
if [ -f connector/installer/build.ps1 ]; then
  awk 'NR>=100 && NR<=130 { printf "%6d\t%s\n", NR, $0 }' connector/installer/build.ps1
fi

echo
echo "== PowerShell / pip / exit handling references =="
if [ -f connector/installer/build.ps1 ]; then
  grep -nE 'LASTEXITCODE|Set-StrictMode|ExitCode|pip|PyInstaller|try|catch|throw|exit ' connector/installer/build.ps1 || true
fi

echo
echo "== repository shell availability =="
command -v bash || true
command -v awk || true
command -v sed || true
command -v python3 || true
command -v node || true

Repository: baasith6/camera-phase-1

Length of output: 3206


🌐 Web query:

PowerShell & executable native program non-zero exit code does not stop script LASTEXITCODE

💡 Result:

By default, PowerShell does not stop execution when a native (external) executable returns a non-zero exit code [1][2]. This occurs because native programs operate outside of PowerShell's internal error-handling system, which typically relies on exceptions [1][3]. Instead, native programs report success or failure solely through the $LASTEXITCODE automatic variable, where 0 conventionally indicates success and non-zero values indicate failure [1][4][3]. To make your script stop automatically on a non-zero exit code, you have the following primary options: Option 1: Modern PowerShell (7.4+) In PowerShell 7.4 and later, you can enable native error integration by setting the $PSNativeCommandUseErrorActionPreference preference variable to $true [1][3]. When this is active, PowerShell treats non-zero exit codes as non-terminating errors that respect the $ErrorActionPreference variable [1][5]. By setting $ErrorActionPreference = 'Stop', these errors become script-terminating and will halt execution [1][6]. $PSNativeCommandUseErrorActionPreference = $true $ErrorActionPreference = 'Stop' & your-executable.exe Option 2: Explicit Check (Compatible with all versions) For versions older than 7.4, or if you prefer explicit control, you must manually check the exit code immediately after the command runs [2][7]. The most common approach is to verify $LASTEXITCODE or the success status variable $? [4][8]. & your-executable.exe if ($LASTEXITCODE -ne 0) { throw "Command failed with exit code $LASTEXITCODE" } Alternatively, you can use the $? variable, which is set to $true if the last operation succeeded (exit code 0) and $false otherwise [4][3]. & your-executable.exe if (-not $?) { throw "Command failed" } Summary of Key Variables: - $LASTEXITCODE: Stores the numeric exit code of the last native program [1][9]. - $?: A boolean that is true if the last operation (cmdlet or native program) succeeded, and false if it failed [4][3]. - $ErrorActionPreference: Determines how PowerShell reacts to errors; setting it to 'Stop' is critical for terminating execution automatically when using modern native command integration [1][3].

Citations:


🏁 Script executed:

#!/bin/sh
set -euo pipefail

echo "== PowerShell handling at top of build.ps1 =="
awk 'NR>=1 && NR<=90 { printf "%6d\t%s\n", NR, $0 }' connector/installer/build.ps1

echo
echo "== explicit native-command error handling settings in repository =="
grep -rnE 'PSNativeCommandUseErrorActionPreference|ErrorActionPreference|\$Err[A-Za-z]+ErrorActionPreference' . || true

echo
echo "== pip install invocations and immediate status checks in repository =="
grep -rnE '& \$python -m pip install|\$python -m pip install|\$LASTEXITCODE|\$?' connector/installer/build.ps1 || true

Repository: baasith6/camera-phase-1

Length of output: 11928


Fail immediately when dependency installation fails.

The $ErrorActionPreference = "Stop" setting does not affect a non-zero native pip exit code, so the script can continue to PyInstaller with stale or incomplete dependencies. Check $LASTEXITCODE immediately after the pip install command.

Proposed fix
 & $python -m pip install -q -r (Join-Path $ConnectorRoot "requirements.txt") -r (Join-Path $ConnectorRoot "requirements-build.txt")
+if ($LASTEXITCODE -ne 0) {
+    throw "Dependency installation failed (exit $LASTEXITCODE)."
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Write-Host "==> PyInstaller deps..."
& $python -m pip install -q -r (Join-Path $ConnectorRoot "requirements.txt") -r (Join-Path $ConnectorRoot "requirements-build.txt")
Write-Host "==> PyInstaller..."
Push-Location $ConnectorRoot
try {
& python -m PyInstaller --noconfirm --clean --distpath $DistDir --workpath (Join-Path $DistDir "build") $SpecFile
& $python -m PyInstaller --noconfirm --clean --distpath $DistDir --workpath (Join-Path $DistDir "build") $SpecFile
Write-Host "==> PyInstaller deps..."
& $python -m pip install -q -r (Join-Path $ConnectorRoot "requirements.txt") -r (Join-Path $ConnectorRoot "requirements-build.txt")
if ($LASTEXITCODE -ne 0) {
throw "Dependency installation failed (exit $LASTEXITCODE)."
}
Write-Host "==> PyInstaller..."
Push-Location $ConnectorRoot
try {
& $python -m PyInstaller --noconfirm --clean --distpath $DistDir --workpath (Join-Path $DistDir "build") $SpecFile
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@connector/installer/build.ps1` around lines 112 - 118, Update the dependency
installation step before the PyInstaller invocation to check $LASTEXITCODE
immediately after the pip command and stop with a nonzero failure when
installation fails. Keep the existing PyInstaller flow unchanged for successful
dependency installation.

if ($LASTEXITCODE -ne 0) {
throw "PyInstaller build failed (exit $LASTEXITCODE). Ensure pip install -r requirements-build.txt was run."
}
Expand All @@ -81,7 +127,7 @@ if (-not (Test-Path $pyinstallerOut)) {
}
Write-Host "==> Built $pyinstallerOut"

$iscc = Find-ISCC
$iscc = Find-ISCC $IsccPath
Write-Host "==> Inno Setup: $iscc"
& $iscc $IssFile
if ($LASTEXITCODE -ne 0) { throw "Inno Setup failed (exit $LASTEXITCODE)" }
Expand Down
2 changes: 0 additions & 2 deletions dashboard/src/app/pages/setup/setup.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,6 @@ import { Camera, Connector, InstallerInfo, Store, Zone } from '../../core/models
{{ testingStream ? 'Testing…' : '🔌 Test Stream' }}
</button>
@if (selectedCamera.id) {
<a class="btn-link" [href]="'http://' + connectorAdminHost + ':8099/snapshot?camera_id=' + selectedCamera.id" target="_blank">
@if (selectedCamera.onvifHost) {
<a class="btn-link" [href]="liveSnapshotUrl" target="_blank">
📷 Live Snapshot
</a>
Expand Down
31 changes: 31 additions & 0 deletions docs/AZURE_MVP_DEPLOY.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,35 @@ Manual deploy: Actions → **Deploy MVP (Azure)** → Run workflow.
- [ ] Shop PC: `Test-NetConnection <VM_IP> -Port 9000` succeeds (MinIO clip uploads)
- [ ] Connector online on Setup page
- [ ] Test alert + email (`SMTP_ENABLE=true`)
- [ ] Cloud-ai YOLOE prompts loaded (see section 4b below)

## 4b. Verify cloud-ai / YOLOE prompts

The `cloud-ai` worker uses **YOLOE** open-vocabulary prompts baked into [`cloud-ai/app/detector.py`](../cloud-ai/app/detector.py) unless `CLOUD_AI_YOLOE_PROMPTS` is set in `.env`.

**Default (recommended):** leave `CLOUD_AI_YOLOE_PROMPTS` empty — 12 prompt phrases including jacket concealment (`concealment` cue).

| Prompt phrase | Production cue | Old eval JSON cue |
|---------------|------------------|-------------------|
| person hiding item inside jacket | `concealment` | `open_bag` |
| person putting object under clothing | `concealment` | `open_bag` |
| hand inside jacket | `concealment` | `open_bag` |
| All other 9 prompts | same | same |

[`cloud-ai/eval/results_jacket_prompts.json`](../cloud-ai/eval/results_jacket_prompts.json) is a **local eval artifact**, not read at runtime. Re-run `python -m eval.run_jacket_test` after prompt changes to refresh it.

**On the VM after deploy:**

```bash
cd /opt/onevo/app
docker logs app-cloud-ai-1 --tail 30
# Expect: "YOLOE prompts (12): person, backpack, ..." and no repeated Redis socket timeouts

docker exec app-cloud-ai-1 python -c "from app.detector import DEFAULT_YOLOE_PROMPTS; print(len(DEFAULT_YOLOE_PROMPTS))"
# Expect: 12
```

**End-to-end:** upload a clip from the connector → `docker logs app-cloud-ai-1` shows `processing clip ...` → alert in dashboard if score ≥ 70.

## 5. Troubleshooting

Expand All @@ -133,6 +162,8 @@ Manual deploy: Actions → **Deploy MVP (Azure)** → Run workflow.
| Deploy SSH fails | Verify `VM_SSH_KEY`, NSG allows SSH from GitHub Actions IPs (or use self-hosted runner in same VNet) |
| Clip upload timeout (`:9000`) | NSG must allow **9000**; set `S3_PUBLIC_ENDPOINT=http://<VM_IP>:9000` in `.env`; test `curl http://<VM_IP>:9000/minio/health/live` from shop PC |
| Connector `disk_critical` on shop PC | Free C: drive space; clear `%ProgramData%\ONEVO\Connector\data\clips` |
| Cloud-ai `Timeout reading from socket` | Fixed in cloud-ai worker (Redis `socket_timeout=None`); redeploy cloud-ai image |
| YOLOE prompts not as expected | Check startup log for prompt list; override via `CLOUD_AI_YOLOE_PROMPTS` only if needed |

## 6. Files reference

Expand Down
4 changes: 2 additions & 2 deletions docs/JENKINS_DEPLOY.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Deploy ONEVO to the Azure MVP VM from your local Windows PC using the root [`Jen

1. [Jenkins LTS](https://www.jenkins.io/download/) installed on Windows.
2. **Git** and **OpenSSH client** (Windows 10+ optional feature or Git for Windows).
3. **.NET 8 SDK**, **Node.js 20**, **Python 3.11+** on the Jenkins agent (same PC).
3. **.NET 8 SDK**, **Node.js 20**, **Python 3.11+** on the Jenkins agent (same PC). Set `ONEVO_PYTHON` in the Jenkinsfile to your Python exe — Jenkins service account often has no `python` on PATH.
4. **Inno Setup 6** + PyInstaller deps for connector installer (see [`connector/installer/INSTALL.md`](../connector/installer/INSTALL.md)).
5. SSH private key that can log in as `azureuser@20.193.69.220`. On Windows OpenSSH, use your **RSA** key (`id_rsa`) in Jenkins — explicit `-i` with `id_ed25519` often fails even when plain `ssh` works.

Expand Down Expand Up @@ -91,7 +91,7 @@ powershell -ExecutionPolicy Bypass -File scripts/deploy-vm.ps1 -SkipInstaller
| SSH permission denied | Re-check `onevo-vm-ssh-key` credential; deploy script copies the key with strict ACLs for OpenSSH on Windows |
| GPU compose error | Keep `USE_GPU=false` on CPU VM |
| Missing `ffmpeg.exe` | First build auto-downloads to `%ProgramData%\onevo\installer-tools\`; or run `scripts/ensure-installer-tools.ps1` once |
| Installer build fails | Install Inno Setup 6 + PyInstaller; run `scripts/build-installer.ps1` manually once |
| Installer build fails | Install **Inno Setup 6** (user or Program Files); set `ONEVO_ISCC` in Jenkinsfile if needed |
| Backend unhealthy after deploy | SSH to VM: `cd /opt/onevo/app && docker compose logs backend --tail 50` |

## Related
Expand Down
4 changes: 4 additions & 0 deletions infra/mvp/.env.production.example
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ CLOUD_AI_BACKEND_URL=http://backend:8080
CLOUD_AI_MODEL_BACKEND=yoloe
CLOUD_AI_MODEL=yoloe-11s-seg.pt
CLOUD_AI_DEVICE=cuda
# Optional YOLOE prompt->cue overrides (comma-separated prompt=cue pairs).
# Leave blank to use built-in defaults in cloud-ai/app/detector.py (12 retail prompts;
# jacket phrases map to concealment, not open_bag). See docs/AZURE_MVP_DEPLOY.md.
# CLOUD_AI_YOLOE_PROMPTS=person=person,open bag=open_bag,person hiding item inside jacket=concealment

# ---- CORS (dashboard origin) ----
CORS_ORIGINS=https://app.yourdomain.example
Expand Down
12 changes: 7 additions & 5 deletions scripts/build-installer.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
param(
[Parameter(Mandatory = $true)]
[string]$BackendUrl,
[string]$PythonPath = "",
[string]$IsccPath = "",
[switch]$AllowHttp
)

Expand All @@ -14,11 +16,11 @@ if (-not (Test-Path $buildScript)) {
}

Write-Host "Building ONEVO connector installer for $BackendUrl ..."
if ($AllowHttp) {
& $buildScript -BackendUrl $BackendUrl -AllowHttp
} else {
& $buildScript -BackendUrl $BackendUrl
}
$buildArgs = @{ BackendUrl = $BackendUrl }
if ($PythonPath) { $buildArgs.PythonPath = $PythonPath }
if ($IsccPath) { $buildArgs.IsccPath = $IsccPath }
if ($AllowHttp) { $buildArgs.AllowHttp = $true }
& $buildScript @buildArgs

$dist = Join-Path $root "connector\dist"
Get-ChildItem $dist -Filter "ONEVO-Connector-Setup-*.exe" | ForEach-Object {
Expand Down
7 changes: 6 additions & 1 deletion scripts/deploy-vm.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ param(
[string]$VmAppDir = "/opt/onevo/app",
[string]$BackendUrl = "http://20.193.69.220:8081",
[string]$SshKeyPath = "",
[string]$PythonPath = "",
[string]$IsccPath = "",
[switch]$SkipInstaller,
[switch]$SkipBuild,
[switch]$UseGpu
Expand Down Expand Up @@ -81,7 +83,10 @@ if (-not $SkipInstaller) {
Write-Host "==> Ensuring installer tools (ffmpeg, WinSW)..."
& (Join-Path $root "scripts\ensure-installer-tools.ps1")
Write-Host "==> Building Windows installer..."
& (Join-Path $root "scripts\build-installer.ps1") -BackendUrl $BackendUrl -AllowHttp
$installerArgs = @{ BackendUrl = $BackendUrl; AllowHttp = $true }
if ($PythonPath) { $installerArgs.PythonPath = $PythonPath }
if ($IsccPath) { $installerArgs.IsccPath = $IsccPath }
& (Join-Path $root "scripts\build-installer.ps1") @installerArgs
Comment on lines +86 to +89

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Restore HTTPS enforcement for public connector traffic. The deployment always passes -AllowHttp, while the baked fallback is a public http:// URL; installers built through this path therefore accept and use unencrypted backend traffic.

  • scripts/deploy-vm.ps1#L86-L89: make HTTP an explicit opt-in switch rather than always setting AllowHttp = $true; use an HTTPS backend by default.
  • connector/app/baked_config.py#L2-L2: do not commit a public plaintext fallback URL; bake the HTTPS endpoint during the installer build.
📍 Affects 2 files
  • scripts/deploy-vm.ps1#L86-L89 (this comment)
  • connector/app/baked_config.py#L2-L2
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/deploy-vm.ps1` around lines 86 - 89, Update scripts/deploy-vm.ps1
lines 86-89 so the installer arguments do not always set AllowHttp to true;
expose HTTP as an explicit opt-in while defaulting BackendUrl to HTTPS. Update
connector/app/baked_config.py line 2 to remove the public plaintext fallback and
bake the HTTPS endpoint during installer builds.

$installerExe = Get-ChildItem (Join-Path $root "connector\dist\ONEVO-Connector-Setup-*.exe") |
Sort-Object LastWriteTime -Descending | Select-Object -First 1
if (-not $installerExe) { throw "Installer EXE not found after build" }
Expand Down
Loading