@trillboards/edge-sdk 0.2.140 → 0.2.150
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/deploy/detector/pyproject.toml +20 -2
- package/deploy/detector/tests/test_execution.py +124 -0
- package/deploy/detector/tests/test_model_identity.py +80 -0
- package/deploy/detector/tests/test_registry.py +18 -0
- package/deploy/detector/tests/test_server.py +3 -1
- package/deploy/detector/tests/test_version_stamp.py +4 -1
- package/deploy/detector/trillboards_detector/_version.py +2 -2
- package/deploy/detector/trillboards_detector/cli.py +3 -0
- package/deploy/detector/trillboards_detector/detections.py +7 -0
- package/deploy/detector/trillboards_detector/errors.py +4 -0
- package/deploy/detector/trillboards_detector/execution.py +109 -0
- package/deploy/detector/trillboards_detector/loader.py +6 -3
- package/deploy/detector/trillboards_detector/registry.py +27 -5
- package/deploy/detector/trillboards_detector/server.py +29 -2
- package/deploy/detector/trillboards_detector/session.py +15 -3
- package/deploy/litertlm/test_trillboards_litertlm_shim.py +81 -0
- package/deploy/litertlm/test_verify_model_proof.py +30 -0
- package/deploy/litertlm/trillboards_litertlm_shim.py +150 -1
- package/deploy/litertlm/verify_model_proof.py +30 -0
- package/deploy/macos/build-macos-pkg.sh +10 -10
- package/deploy/macos/scripts/setup-detector.sh +2 -2
- package/deploy/macos/scripts/setup-litertlm.sh +66 -30
- package/deploy/macos/test-setup-litertlm-reinstall.sh +57 -4
- package/deploy/systemd/setup-detector.sh +2 -2
- package/deploy/systemd/setup-litertlm.sh +51 -27
- package/deploy/windows/build-windows.ps1 +10 -6
- package/deploy/windows/bundle-windows-installer.ps1 +3 -3
- package/deploy/windows/e2e/test-local-sense.ps1 +6 -5
- package/deploy/windows/install-update.ps1 +52 -9
- package/deploy/windows/set-executable-icon.ps1 +24 -5
- package/deploy/windows/setup-detector.ps1 +142 -4
- package/deploy/windows/setup-litertlm.ps1 +53 -10
- package/dist/AudienceAnalyzeClient.d.ts +10 -0
- package/dist/AudienceAnalyzeClient.d.ts.map +1 -1
- package/dist/AudienceAnalyzeClient.js +5 -4
- package/dist/AudienceAnalyzeClient.js.map +1 -1
- package/dist/CloudSensingManager.d.ts +38 -1
- package/dist/CloudSensingManager.d.ts.map +1 -1
- package/dist/CloudSensingManager.js +403 -25
- package/dist/CloudSensingManager.js.map +1 -1
- package/dist/DetectorFieldProducer.d.ts +5 -1
- package/dist/DetectorFieldProducer.d.ts.map +1 -1
- package/dist/DetectorFieldProducer.js +8 -1
- package/dist/DetectorFieldProducer.js.map +1 -1
- package/dist/DurableVlmCandidateSpool.d.ts +83 -2
- package/dist/DurableVlmCandidateSpool.d.ts.map +1 -1
- package/dist/DurableVlmCandidateSpool.js +596 -30
- package/dist/DurableVlmCandidateSpool.js.map +1 -1
- package/dist/EdgeAgent.js +1 -1
- package/dist/FrameGroupingShadow.d.ts +51 -0
- package/dist/FrameGroupingShadow.d.ts.map +1 -0
- package/dist/FrameGroupingShadow.js +158 -0
- package/dist/FrameGroupingShadow.js.map +1 -0
- package/dist/ImageClassifierFieldProducer.d.ts +36 -0
- package/dist/ImageClassifierFieldProducer.d.ts.map +1 -0
- package/dist/ImageClassifierFieldProducer.js +150 -0
- package/dist/ImageClassifierFieldProducer.js.map +1 -0
- package/dist/LiteRtVlmProvider.d.ts +5 -0
- package/dist/LiteRtVlmProvider.d.ts.map +1 -1
- package/dist/LiteRtVlmProvider.js +72 -7
- package/dist/LiteRtVlmProvider.js.map +1 -1
- package/dist/LocalDetectorClient.d.ts +6 -0
- package/dist/LocalDetectorClient.d.ts.map +1 -1
- package/dist/LocalDetectorClient.js +8 -0
- package/dist/LocalDetectorClient.js.map +1 -1
- package/dist/OrderedFrameReview.d.ts +57 -0
- package/dist/OrderedFrameReview.d.ts.map +1 -0
- package/dist/OrderedFrameReview.js +121 -0
- package/dist/OrderedFrameReview.js.map +1 -0
- package/dist/OrderedReviewClient.d.ts +38 -0
- package/dist/OrderedReviewClient.d.ts.map +1 -0
- package/dist/OrderedReviewClient.js +121 -0
- package/dist/OrderedReviewClient.js.map +1 -0
- package/dist/OrderedSequenceStore.d.ts +69 -0
- package/dist/OrderedSequenceStore.d.ts.map +1 -0
- package/dist/OrderedSequenceStore.js +257 -0
- package/dist/OrderedSequenceStore.js.map +1 -0
- package/dist/ProfileReviewWork.d.ts +68 -0
- package/dist/ProfileReviewWork.d.ts.map +1 -0
- package/dist/ProfileReviewWork.js +150 -0
- package/dist/ProfileReviewWork.js.map +1 -0
- package/dist/RetainedFieldContract.d.ts +21 -0
- package/dist/RetainedFieldContract.d.ts.map +1 -0
- package/dist/RetainedFieldContract.js +92 -0
- package/dist/RetainedFieldContract.js.map +1 -0
- package/dist/ReviewModelUse.d.ts +7 -0
- package/dist/ReviewModelUse.d.ts.map +1 -0
- package/dist/ReviewModelUse.js +30 -0
- package/dist/ReviewModelUse.js.map +1 -0
- package/dist/ReviewPriority.d.ts +12 -0
- package/dist/ReviewPriority.d.ts.map +1 -0
- package/dist/ReviewPriority.js +45 -0
- package/dist/ReviewPriority.js.map +1 -0
- package/dist/RuntimeModelSelection.d.ts +10 -0
- package/dist/RuntimeModelSelection.d.ts.map +1 -0
- package/dist/RuntimeModelSelection.js +58 -0
- package/dist/RuntimeModelSelection.js.map +1 -0
- package/dist/SceneChangeDetector.d.ts +1 -1
- package/dist/SceneChangeDetector.d.ts.map +1 -1
- package/dist/SceneChangeDetector.js +18 -6
- package/dist/SceneChangeDetector.js.map +1 -1
- package/dist/ScopedVlmReview.d.ts +76 -0
- package/dist/ScopedVlmReview.d.ts.map +1 -0
- package/dist/ScopedVlmReview.js +267 -0
- package/dist/ScopedVlmReview.js.map +1 -0
- package/dist/SensingProfileClient.d.ts +50 -0
- package/dist/SensingProfileClient.d.ts.map +1 -1
- package/dist/SensingProfileClient.js +58 -2
- package/dist/SensingProfileClient.js.map +1 -1
- package/dist/TemporalReviewClient.d.ts +43 -0
- package/dist/TemporalReviewClient.d.ts.map +1 -0
- package/dist/TemporalReviewClient.js +117 -0
- package/dist/TemporalReviewClient.js.map +1 -0
- package/dist/TemporalReviewJournal.d.ts +91 -0
- package/dist/TemporalReviewJournal.d.ts.map +1 -0
- package/dist/TemporalReviewJournal.js +337 -0
- package/dist/TemporalReviewJournal.js.map +1 -0
- package/dist/UrgentReviewTriage.d.ts +35 -0
- package/dist/UrgentReviewTriage.d.ts.map +1 -0
- package/dist/UrgentReviewTriage.js +52 -0
- package/dist/UrgentReviewTriage.js.map +1 -0
- package/dist/VideoSegmentUploader.d.ts +293 -0
- package/dist/VideoSegmentUploader.d.ts.map +1 -0
- package/dist/VideoSegmentUploader.js +1002 -0
- package/dist/VideoSegmentUploader.js.map +1 -0
- package/dist/VlmCandidateBuffer.d.ts +19 -0
- package/dist/VlmCandidateBuffer.d.ts.map +1 -1
- package/dist/VlmCandidateBuffer.js.map +1 -1
- package/dist/autoSense.d.ts +2 -2
- package/dist/autoSense.d.ts.map +1 -1
- package/dist/autoSense.js +16 -11
- package/dist/autoSense.js.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/continuousReview.d.ts +71 -0
- package/dist/continuousReview.d.ts.map +1 -0
- package/dist/continuousReview.js +53 -0
- package/dist/continuousReview.js.map +1 -0
- package/dist/detectorCountReview.d.ts +9 -0
- package/dist/detectorCountReview.d.ts.map +1 -0
- package/dist/detectorCountReview.js +79 -0
- package/dist/detectorCountReview.js.map +1 -0
- package/dist/diagnose.d.ts +1 -1
- package/dist/diagnose.js +1 -1
- package/dist/installerOnboardingMode.d.ts.map +1 -1
- package/dist/installerOnboardingMode.js +5 -3
- package/dist/installerOnboardingMode.js.map +1 -1
- package/dist/localInferenceSchema.d.ts +8 -0
- package/dist/localInferenceSchema.d.ts.map +1 -1
- package/dist/localInferenceSchema.js +3 -3
- package/dist/localInferenceSchema.js.map +1 -1
- package/dist/localSenseE2eHarness.d.ts +12 -0
- package/dist/localSenseE2eHarness.d.ts.map +1 -1
- package/dist/localSenseE2eHarness.js +77 -4
- package/dist/localSenseE2eHarness.js.map +1 -1
- package/dist/localSensing.d.ts +91 -18
- package/dist/localSensing.d.ts.map +1 -1
- package/dist/localSensing.js +678 -153
- package/dist/localSensing.js.map +1 -1
- package/dist/mixedScopedCounts.d.ts +17 -0
- package/dist/mixedScopedCounts.d.ts.map +1 -0
- package/dist/mixedScopedCounts.js +90 -0
- package/dist/mixedScopedCounts.js.map +1 -0
- package/dist/sensingCatchRate.d.ts +9 -5
- package/dist/sensingCatchRate.d.ts.map +1 -1
- package/dist/sensingCatchRate.js +10 -6
- package/dist/sensingCatchRate.js.map +1 -1
- package/dist/sensingRunMode.d.ts +5 -0
- package/dist/sensingRunMode.d.ts.map +1 -1
- package/dist/sensingRunMode.js +56 -0
- package/dist/sensingRunMode.js.map +1 -1
- package/package.json +7 -4
- package/retained/ArchiveLifecycle.cjs +79 -0
- package/retained/ModelStageScheduler.cjs +66 -0
- package/retained/ProfileAuthorityClient.cjs +88 -0
- package/retained/RetainedCaptureClient.cjs +164 -0
- package/retained/WaitRecheckCaptureClient.cjs +109 -0
- package/retained/__tests__/archive-lifecycle.test.cjs +117 -0
- package/retained/__tests__/runtime.test.cjs +384 -0
- package/retained/alert-rule-policy.cjs +37 -0
- package/retained/manager.cjs +133 -0
- package/retained/pilot-profile-client.cjs +82 -0
- package/retained/temporal-grouping.cjs +78 -0
- package/retained/website-api-capability.cjs +122 -0
|
@@ -29,6 +29,9 @@ LA_DIR="$USER_HOME/Library/LaunchAgents"
|
|
|
29
29
|
LOCAL_BIN="$USER_HOME/.local/bin"
|
|
30
30
|
MODEL_ID="gemma-4-E2B-it.litertlm"
|
|
31
31
|
HF_REPO="litert-community/gemma-4-E2B-it-litert-lm"
|
|
32
|
+
MODEL_REVISION="84b6978eff6e4eea02825bc2ee4ea48579f13109"
|
|
33
|
+
APPROVED_MODEL_SHA256="ab7838cdfc8f77e54d8ca45eadceb20452d9f01e4bfade03e5dce27911b27e42"
|
|
34
|
+
MODEL_URL="https://huggingface.co/$HF_REPO/resolve/$MODEL_REVISION/$MODEL_ID"
|
|
32
35
|
SERVE_LABEL="com.trillboards.litertlm"
|
|
33
36
|
SERVE_PORT="9379"
|
|
34
37
|
LITERT_PYTHON="${LITERT_LM_PYTHON:-3.12}"
|
|
@@ -245,16 +248,29 @@ install_litertlm_shim() {
|
|
|
245
248
|
# Deliberately NOT a /v1/models check: that answers 200 without ever building an
|
|
246
249
|
# engine, so it cannot tell a working GPU encoder from one that fails on first
|
|
247
250
|
# use.
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
251
|
+
verify_litertlm_probe() {
|
|
252
|
+
local requested="$1" body response code tool_dir py verifier candidate valid=1
|
|
253
|
+
body="$LITERT_DIR/.identity-probe.json"
|
|
254
|
+
response="$LITERT_DIR/.identity-response.json"
|
|
255
|
+
printf '{"model":"%s","messages":[{"role":"user","content":[{"type":"text","text":"Reply with the single word: ok"},{"type":"image_url","image_url":{"url":"data:image/jpeg;base64,%s"}}]}],"max_completion_tokens":8,"temperature":0}' \
|
|
256
|
+
"$requested" "$LITERTLM_PROBE_JPEG_B64" > "$body" 2>/dev/null || return 1
|
|
257
|
+
code=$(curl -s -o "$response" -w '%{http_code}' --max-time 180 \
|
|
253
258
|
-X POST "http://127.0.0.1:$SERVE_PORT/v1/chat/completions" \
|
|
254
|
-
-H 'Content-Type: application/json'
|
|
255
|
-
|
|
256
|
-
[ "$
|
|
259
|
+
-H 'Content-Type: application/json' -H "X-Trillboards-Model-Sha256: $APPROVED_MODEL_SHA256" \
|
|
260
|
+
--data-binary @"$body" 2>/dev/null)
|
|
261
|
+
if [ "$code" = "200" ]; then
|
|
262
|
+
tool_dir="$("${UV:-$LOCAL_BIN/uv}" tool dir 2>/dev/null | tr -d '\r')"
|
|
263
|
+
py="$tool_dir/litert-lm/bin/python"
|
|
264
|
+
for candidate in "$SCRIPT_DIR/verify_model_proof.py" "$SCRIPT_DIR/../litertlm/verify_model_proof.py" "$SCRIPT_DIR/../../litertlm/verify_model_proof.py"; do
|
|
265
|
+
[ -f "$candidate" ] && { verifier="$candidate"; break; }
|
|
266
|
+
done
|
|
267
|
+
[ -n "${verifier:-}" ] && [ -x "$py" ] \
|
|
268
|
+
&& "$py" "$verifier" "$response" "$requested" "$APPROVED_MODEL_SHA256" && valid=0
|
|
269
|
+
fi
|
|
270
|
+
rm -f "$body" "$response" 2>/dev/null || true
|
|
271
|
+
return "$valid"
|
|
257
272
|
}
|
|
273
|
+
verify_litertlm_vision() { verify_litertlm_probe "$MODEL_ID,gpu"; }
|
|
258
274
|
|
|
259
275
|
# The same probe, but asking for NO delegate suffix rather than `,gpu`.
|
|
260
276
|
#
|
|
@@ -265,16 +281,7 @@ verify_litertlm_vision() {
|
|
|
265
281
|
# carries no suffix at all, so a 200 means the box built an engine and ran a real
|
|
266
282
|
# image through it under the config now on disk, which is exactly the claim each
|
|
267
283
|
# rung needs.
|
|
268
|
-
verify_litertlm_plain_inference() {
|
|
269
|
-
_body="$LITERT_DIR/.plain-probe.json"
|
|
270
|
-
printf '{"model":"%s","messages":[{"role":"user","content":[{"type":"text","text":"Reply with the single word: ok"},{"type":"image_url","image_url":{"url":"data:image/jpeg;base64,%s"}}]}],"max_completion_tokens":8,"temperature":0}' \
|
|
271
|
-
"$MODEL_ID" "$LITERTLM_PROBE_JPEG_B64" > "$_body" 2>/dev/null || return 1
|
|
272
|
-
_code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 180 \
|
|
273
|
-
-X POST "http://127.0.0.1:$SERVE_PORT/v1/chat/completions" \
|
|
274
|
-
-H 'Content-Type: application/json' --data-binary @"$_body" 2>/dev/null)
|
|
275
|
-
rm -f "$_body" 2>/dev/null || true
|
|
276
|
-
[ "$_code" = "200" ]
|
|
277
|
-
}
|
|
284
|
+
verify_litertlm_plain_inference() { verify_litertlm_probe "$MODEL_ID"; }
|
|
278
285
|
|
|
279
286
|
local_ai_healthy() {
|
|
280
287
|
curl -fsS --max-time 3 "http://127.0.0.1:$SERVE_PORT/v1/models" 2>/dev/null \
|
|
@@ -334,11 +341,33 @@ verify_bundled_model() {
|
|
|
334
341
|
[ -f "$BUNDLED_MODEL_SHA" ] || fail "bundled model checksum is missing"
|
|
335
342
|
EXPECTED_SHA="$(awk 'NR == 1 { print $1 }' "$BUNDLED_MODEL_SHA" 2>/dev/null || echo '')"
|
|
336
343
|
ACTUAL_SHA="$(shasum -a 256 "$BUNDLED_MODEL" 2>/dev/null | awk '{print $1}')"
|
|
337
|
-
if [
|
|
344
|
+
if [ "$EXPECTED_SHA" != "$APPROVED_MODEL_SHA256" ] || [ "$ACTUAL_SHA" != "$APPROVED_MODEL_SHA256" ]; then
|
|
338
345
|
fail "bundled model checksum mismatch"
|
|
339
346
|
fi
|
|
340
347
|
log "verified bundled model SHA-256"
|
|
341
348
|
}
|
|
349
|
+
installed_model_verified() {
|
|
350
|
+
[ -f "$STORE_DIR/model.litertlm" ] || return 1
|
|
351
|
+
[ "$(shasum -a 256 "$STORE_DIR/model.litertlm" 2>/dev/null | awk '{print $1}')" = "$APPROVED_MODEL_SHA256" ]
|
|
352
|
+
}
|
|
353
|
+
import_verified_model() {
|
|
354
|
+
local source="$1" old_sha rollback
|
|
355
|
+
[ "$(shasum -a 256 "$source" 2>/dev/null | awk '{print $1}')" = "$APPROVED_MODEL_SHA256" ] || return 1
|
|
356
|
+
if [ -f "$STORE_DIR/model.litertlm" ] && ! installed_model_verified; then
|
|
357
|
+
old_sha="$(shasum -a 256 "$STORE_DIR/model.litertlm" | awk '{print $1}')"
|
|
358
|
+
rollback="$STORE_DIR/rollback/$old_sha.litertlm"
|
|
359
|
+
mkdir -p "$STORE_DIR/rollback" || return 1
|
|
360
|
+
[ -f "$rollback" ] || cp "$STORE_DIR/model.litertlm" "$rollback" || return 1
|
|
361
|
+
[ "$(shasum -a 256 "$rollback" | awk '{print $1}')" = "$old_sha" ] || return 1
|
|
362
|
+
if launch_agent_loaded; then
|
|
363
|
+
launchctl bootout "gui/$(id -u)/$SERVE_LABEL" >>"$LOG" 2>&1 || return 1
|
|
364
|
+
wait_for_launch_agent_removal || return 1
|
|
365
|
+
fi
|
|
366
|
+
log "previous model preserved at $rollback"
|
|
367
|
+
fi
|
|
368
|
+
"$LITERTLM" import "$source" "$MODEL_ID" >>"$LOG" 2>&1 || return 1
|
|
369
|
+
installed_model_verified
|
|
370
|
+
}
|
|
342
371
|
|
|
343
372
|
# One provisioning process per user. Re-opening the pairing helper is a normal
|
|
344
373
|
# recovery path, so duplicate launches must collapse without duplicate downloads.
|
|
@@ -351,7 +380,7 @@ if ! mkdir "$LOCK_DIR" 2>/dev/null; then
|
|
|
351
380
|
log "LiteRT-LM provisioning is already running (pid $LOCK_PID)"
|
|
352
381
|
if [ "$REQUIRE_BUNDLED_MODEL" = "1" ]; then
|
|
353
382
|
for _attempt in $(seq 1 120); do
|
|
354
|
-
local_ai_healthy && exit 0
|
|
383
|
+
local_ai_healthy && installed_model_verified && (verify_litertlm_vision || verify_litertlm_plain_inference) && exit 0
|
|
355
384
|
sleep 2
|
|
356
385
|
done
|
|
357
386
|
fail "the existing local AI installation did not become healthy"
|
|
@@ -468,12 +497,12 @@ fi
|
|
|
468
497
|
|
|
469
498
|
install_litertlm_shim || true
|
|
470
499
|
|
|
471
|
-
# 3. Model -
|
|
472
|
-
#
|
|
500
|
+
# 3. Model - skip only if the installed BYTES match the approved artifact.
|
|
501
|
+
# Engineering runs may use the immutable HF revision only with the
|
|
473
502
|
# explicit TRILLBOARDS_ALLOW_MODEL_DOWNLOAD=1 opt-in.
|
|
474
503
|
HAVE_MODEL=0
|
|
475
|
-
if
|
|
476
|
-
log "model $MODEL_ID already present — skipping import"
|
|
504
|
+
if installed_model_verified; then
|
|
505
|
+
log "approved model $MODEL_ID already present — skipping import"
|
|
477
506
|
HAVE_MODEL=1
|
|
478
507
|
if [ -f "$BUNDLED_MODEL" ]; then
|
|
479
508
|
verify_bundled_model
|
|
@@ -483,7 +512,7 @@ if "$LITERTLM" list 2>/dev/null | grep -q "$MODEL_ID"; then
|
|
|
483
512
|
elif [ -f "$BUNDLED_MODEL" ]; then
|
|
484
513
|
verify_bundled_model
|
|
485
514
|
log "importing $MODEL_ID from BUNDLED file $BUNDLED_MODEL (no download) …"
|
|
486
|
-
if
|
|
515
|
+
if import_verified_model "$BUNDLED_MODEL"; then
|
|
487
516
|
log "model imported from bundle"
|
|
488
517
|
HAVE_MODEL=1
|
|
489
518
|
# Reclaim the staged ~2.4 GB — the litert-lm store now holds the model.
|
|
@@ -497,12 +526,19 @@ fi
|
|
|
497
526
|
if [ "$HAVE_MODEL" -eq 0 ]; then
|
|
498
527
|
[ "$REQUIRE_BUNDLED_MODEL" = "1" ] && fail "required bundled model is missing"
|
|
499
528
|
[ "$ALLOW_MODEL_DOWNLOAD" = "1" ] || fail "local AI model is missing and network fallback is disabled"
|
|
500
|
-
|
|
501
|
-
"$
|
|
502
|
-
|
|
503
|
-
|
|
529
|
+
mkdir -p "$STORE_DIR" || fail "model store is unavailable"
|
|
530
|
+
DOWNLOAD="$(mktemp "$STORE_DIR/approved-model.XXXXXX")" || fail "could not create a model download file"
|
|
531
|
+
log "downloading approved $MODEL_ID from immutable revision $MODEL_REVISION"
|
|
532
|
+
if curl -fL --retry 3 --output "$DOWNLOAD" "$MODEL_URL" >>"$LOG" 2>&1 && import_verified_model "$DOWNLOAD"; then
|
|
533
|
+
HAVE_MODEL=1
|
|
534
|
+
rm -f "$DOWNLOAD"
|
|
535
|
+
log "model imported from verified immutable revision"
|
|
536
|
+
else
|
|
537
|
+
rm -f "$DOWNLOAD"
|
|
538
|
+
fail "approved model download/import failed"
|
|
539
|
+
fi
|
|
504
540
|
fi
|
|
505
|
-
[ "$HAVE_MODEL" -eq 1 ] ||
|
|
541
|
+
[ "$HAVE_MODEL" -eq 1 ] || fail "local AI model is unavailable"
|
|
506
542
|
|
|
507
543
|
# Record the installed version next to the store model so the connector's
|
|
508
544
|
# edge-core ModelManager seeds it (readBundledModelVersion) and does not
|
|
@@ -10,9 +10,29 @@ export HOME="$TEST_ROOT/home"
|
|
|
10
10
|
export TRILLBOARDS_TEST_STATE_DIR="$TEST_ROOT/state"
|
|
11
11
|
export LITERT_LM_DIR="$TEST_ROOT/store"
|
|
12
12
|
export TRILLBOARDS_REQUIRE_BUNDLED_MODEL=1
|
|
13
|
+
export TRILLBOARDS_TEST_REAL_PYTHON="${TRILLBOARDS_TEST_REAL_PYTHON:-$(command -v python3)}"
|
|
13
14
|
FAKE_BIN="$HOME/.local/bin"
|
|
14
15
|
SHARE_DIR="$HOME/Library/Trillboards/edge/share"
|
|
15
16
|
mkdir -p "$FAKE_BIN" "$SHARE_DIR" "$TRILLBOARDS_TEST_STATE_DIR"
|
|
17
|
+
mkdir -p "$TRILLBOARDS_TEST_STATE_DIR/tools/litert-lm/bin" "$LITERT_LM_DIR/models/gemma-4-E2B-it.litertlm"
|
|
18
|
+
printf 'fixture evaluated model\n' >"$LITERT_LM_DIR/models/gemma-4-E2B-it.litertlm/model.litertlm"
|
|
19
|
+
|
|
20
|
+
# These are tiny installer fixtures, not model-inference/accuracy tests. Hashes
|
|
21
|
+
# are simulated from file CONTENT, so the same name with old bytes is rejected.
|
|
22
|
+
cat >"$FAKE_BIN/shasum" <<'EOF'
|
|
23
|
+
#!/bin/bash
|
|
24
|
+
file="${@: -1}"
|
|
25
|
+
if [ -f "$file" ] && [ "$(cat "$file")" = 'fixture evaluated model' ]; then
|
|
26
|
+
printf 'ab7838cdfc8f77e54d8ca45eadceb20452d9f01e4bfade03e5dce27911b27e42 %s\n' "$file"
|
|
27
|
+
else
|
|
28
|
+
if [ -x /usr/bin/shasum ]; then /usr/bin/shasum "$@"; else /usr/bin/sha256sum "$file"; fi
|
|
29
|
+
fi
|
|
30
|
+
EOF
|
|
31
|
+
cat >"$TRILLBOARDS_TEST_STATE_DIR/tools/litert-lm/bin/python" <<'EOF'
|
|
32
|
+
#!/bin/bash
|
|
33
|
+
[ "${1:-}" != '-c' ] || exit 0
|
|
34
|
+
exec "$TRILLBOARDS_TEST_REAL_PYTHON" "$@"
|
|
35
|
+
EOF
|
|
16
36
|
|
|
17
37
|
cat >"$FAKE_BIN/uname" <<'EOF'
|
|
18
38
|
#!/bin/bash
|
|
@@ -25,14 +45,20 @@ EOF
|
|
|
25
45
|
|
|
26
46
|
cat >"$FAKE_BIN/uv" <<'EOF'
|
|
27
47
|
#!/bin/bash
|
|
48
|
+
[ "${1:-} ${2:-}" != 'tool dir' ] || printf '%s\n' "$TRILLBOARDS_TEST_STATE_DIR/tools"
|
|
28
49
|
exit 0
|
|
29
50
|
EOF
|
|
30
51
|
|
|
31
52
|
cat >"$FAKE_BIN/litert-lm" <<'EOF'
|
|
32
53
|
#!/bin/bash
|
|
33
|
-
|
|
34
|
-
printf 'gemma-4-E2B-it.litertlm\n'
|
|
35
|
-
|
|
54
|
+
case "${1:-}" in
|
|
55
|
+
list) printf 'gemma-4-E2B-it.litertlm\n' ;;
|
|
56
|
+
--version) printf 'litert-lm 0.16.0\n' ;;
|
|
57
|
+
import)
|
|
58
|
+
cp "$2" "$LITERT_LM_DIR/models/$3/model.litertlm"
|
|
59
|
+
printf 'import\n' >>"$TRILLBOARDS_TEST_STATE_DIR/imports"
|
|
60
|
+
;;
|
|
61
|
+
esac
|
|
36
62
|
exit 0
|
|
37
63
|
EOF
|
|
38
64
|
|
|
@@ -54,15 +80,21 @@ EOF
|
|
|
54
80
|
cat >"$FAKE_BIN/curl" <<'EOF'
|
|
55
81
|
#!/bin/bash
|
|
56
82
|
body=""
|
|
83
|
+
response=""
|
|
57
84
|
prev=""
|
|
58
85
|
for arg in "$@"; do
|
|
59
86
|
case "$prev" in
|
|
60
87
|
--data-binary) body="${arg#@}" ;;
|
|
88
|
+
-o) response="$arg" ;;
|
|
61
89
|
esac
|
|
62
90
|
prev="$arg"
|
|
63
91
|
done
|
|
64
92
|
for arg in "$@"; do
|
|
65
93
|
if [ "$arg" = "-w" ]; then
|
|
94
|
+
model="$(sed -n 's/.*"model":"\([^"]*\)".*/\1/p' "$body")"
|
|
95
|
+
case "$model" in *,gpu|*,cpu) ;; *) model="$model,cpu" ;; esac
|
|
96
|
+
sha="${TRILLBOARDS_TEST_PROOF_SHA:-ab7838cdfc8f77e54d8ca45eadceb20452d9f01e4bfade03e5dce27911b27e42}"
|
|
97
|
+
printf '{"runtimeModel":{"version":1,"format":"litert-lm","sha256":"%s","runtimeModelId":"%s"}}' "$sha" "$model" >"$response"
|
|
66
98
|
if [ "${TRILLBOARDS_TEST_FAIL_WHEN_SPECULATIVE:-0}" = "1" ] \
|
|
67
99
|
&& grep -Fq '"speculative_decoding":true' "$LITERT_LM_DIR/config.json" 2>/dev/null; then
|
|
68
100
|
printf '500'
|
|
@@ -127,7 +159,8 @@ exit 0
|
|
|
127
159
|
EOF
|
|
128
160
|
|
|
129
161
|
chmod +x "$FAKE_BIN/uname" "$FAKE_BIN/uv" "$FAKE_BIN/litert-lm" \
|
|
130
|
-
"$FAKE_BIN/curl" "$FAKE_BIN/sleep" "$FAKE_BIN/launchctl"
|
|
162
|
+
"$FAKE_BIN/curl" "$FAKE_BIN/sleep" "$FAKE_BIN/launchctl" "$FAKE_BIN/shasum" \
|
|
163
|
+
"$TRILLBOARDS_TEST_STATE_DIR/tools/litert-lm/bin/python"
|
|
131
164
|
|
|
132
165
|
cat >"$SHARE_DIR/com.trillboards.litertlm.plist" <<'EOF'
|
|
133
166
|
<plist><string>__LITERTLM__</string><string>__PORT__</string><string>__LOG_DIR__</string></plist>
|
|
@@ -247,5 +280,25 @@ fi
|
|
|
247
280
|
|
|
248
281
|
unset TRILLBOARDS_TEST_FAIL_WHEN_SPECULATIVE
|
|
249
282
|
|
|
283
|
+
# An existing model under the SAME id must be replaced when its bytes differ,
|
|
284
|
+
# with a checked rollback copy. The fixture importer performs a real file copy.
|
|
285
|
+
printf 'fixture old model\n' >"$LITERT_LM_DIR/models/gemma-4-E2B-it.litertlm/model.litertlm"
|
|
286
|
+
old_sha="$("$FAKE_BIN/shasum" -a 256 "$LITERT_LM_DIR/models/gemma-4-E2B-it.litertlm/model.litertlm" | awk '{print $1}')"
|
|
287
|
+
mkdir -p "$SHARE_DIR/models"
|
|
288
|
+
printf 'fixture evaluated model\n' >"$SHARE_DIR/models/gemma-4-E2B-it.litertlm"
|
|
289
|
+
printf 'ab7838cdfc8f77e54d8ca45eadceb20452d9f01e4bfade03e5dce27911b27e42\n' >"$SHARE_DIR/models/gemma-4-E2B-it.litertlm.sha256"
|
|
290
|
+
if ! OUTPUT4="$(/bin/bash "$SETUP" 2>&1)"; then printf '%s\n' "$OUTPUT4" >&2; exit 1; fi
|
|
291
|
+
[ "$(cat "$LITERT_LM_DIR/models/gemma-4-E2B-it.litertlm/model.litertlm")" = 'fixture evaluated model' ]
|
|
292
|
+
[ "$("$FAKE_BIN/shasum" -a 256 "$LITERT_LM_DIR/models/gemma-4-E2B-it.litertlm/rollback/$old_sha.litertlm" | awk '{print $1}')" = "$old_sha" ]
|
|
293
|
+
[ "$(wc -l <"$TRILLBOARDS_TEST_STATE_DIR/imports" | tr -d ' ')" = '1' ]
|
|
294
|
+
# A correct store and a 200 inference must still fail when the loaded engine's
|
|
295
|
+
# identity differs. This catches a stale in-memory model after an upgrade.
|
|
296
|
+
export TRILLBOARDS_TEST_PROOF_SHA="181938105e0eefd105961417e8da75903eacda102c4fce9ce90f50b97139a63c"
|
|
297
|
+
if OUTPUT5="$(/bin/bash "$SETUP" 2>&1)"; then
|
|
298
|
+
printf 'FAIL: setup accepted a stale runtime model proof\n' >&2
|
|
299
|
+
exit 1
|
|
300
|
+
fi
|
|
301
|
+
unset TRILLBOARDS_TEST_PROOF_SHA
|
|
302
|
+
|
|
250
303
|
printf 'macOS LiteRT-LM reinstall race + engine-settings cascade regressions passed\n'
|
|
251
304
|
|
|
@@ -93,7 +93,7 @@ SOURCE="$(find_detector_source)" || { log "ERROR: detector package source not fo
|
|
|
93
93
|
# re-downloads the pinned ~250 MB onnxruntime wheel on a shop PC every time, for
|
|
94
94
|
# nothing — an exact pin cannot resolve to different bytes.
|
|
95
95
|
log "installing/repairing trillboards-detector from $SOURCE with uv-managed Python $DETECTOR_PYTHON"
|
|
96
|
-
if ! "$UV" tool install --python "$DETECTOR_PYTHON" --managed-python --force --refresh-package trillboards-detector "$SOURCE" >>"$LOG" 2>&1; then
|
|
96
|
+
if ! "$UV" tool install --python "$DETECTOR_PYTHON" --managed-python --force --refresh-package trillboards-detector "${SOURCE}[cpu]" >>"$LOG" 2>&1; then
|
|
97
97
|
log "ERROR: trillboards-detector install failed; the cascade stays off"
|
|
98
98
|
exit 0
|
|
99
99
|
fi
|
|
@@ -126,7 +126,7 @@ elif [ "$HAVE_VERSION" != "$WANT_VERSION" ]; then
|
|
|
126
126
|
# asking it again the same way is not a repair.
|
|
127
127
|
log "WARN: the installed detector reports '$HAVE_VERSION' but the staged package is '$WANT_VERSION' - reinstalling from scratch"
|
|
128
128
|
"$UV" tool uninstall trillboards-detector >>"$LOG" 2>&1 || true
|
|
129
|
-
"$UV" tool install --python "$DETECTOR_PYTHON" --managed-python --force --refresh-package trillboards-detector "$SOURCE" >>"$LOG" 2>&1 || true
|
|
129
|
+
"$UV" tool install --python "$DETECTOR_PYTHON" --managed-python --force --refresh-package trillboards-detector "${SOURCE}[cpu]" >>"$LOG" 2>&1 || true
|
|
130
130
|
DETECTOR="$LOCAL_BIN/trillboards-detector"
|
|
131
131
|
[ -x "$DETECTOR" ] || DETECTOR="$(command -v trillboards-detector || echo "$DETECTOR")"
|
|
132
132
|
HAVE_VERSION="$(installed_detector_version)"
|
|
@@ -37,6 +37,9 @@ LOG_DIR="${TRILLBOARDS_LOG_DIR:-/var/log/trillboards}"
|
|
|
37
37
|
LOCAL_BIN="$USER_HOME/.local/bin"
|
|
38
38
|
MODEL_ID="gemma-4-E2B-it.litertlm"
|
|
39
39
|
HF_REPO="litert-community/gemma-4-E2B-it-litert-lm"
|
|
40
|
+
MODEL_REVISION="84b6978eff6e4eea02825bc2ee4ea48579f13109"
|
|
41
|
+
APPROVED_MODEL_SHA256="ab7838cdfc8f77e54d8ca45eadceb20452d9f01e4bfade03e5dce27911b27e42"
|
|
42
|
+
MODEL_URL="https://huggingface.co/$HF_REPO/resolve/$MODEL_REVISION/$MODEL_ID"
|
|
40
43
|
SERVE_PORT="9379"
|
|
41
44
|
SERVE_UNIT="trillboards-litertlm"
|
|
42
45
|
LITERT_PYTHON="${LITERT_LM_PYTHON:-3.12}"
|
|
@@ -253,16 +256,29 @@ install_litertlm_shim() {
|
|
|
253
256
|
# Deliberately NOT a /v1/models check: that answers 200 without ever building an
|
|
254
257
|
# engine, so it cannot tell a working GPU encoder from one that fails on first
|
|
255
258
|
# use.
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
259
|
+
verify_litertlm_probe() {
|
|
260
|
+
local requested="$1" body response code tool_dir py verifier candidate valid=1
|
|
261
|
+
body="$LITERT_DIR/.identity-probe.json"
|
|
262
|
+
response="$LITERT_DIR/.identity-response.json"
|
|
263
|
+
printf '{"model":"%s","messages":[{"role":"user","content":[{"type":"text","text":"Reply with the single word: ok"},{"type":"image_url","image_url":{"url":"data:image/jpeg;base64,%s"}}]}],"max_completion_tokens":8,"temperature":0}' \
|
|
264
|
+
"$requested" "$LITERTLM_PROBE_JPEG_B64" > "$body" 2>/dev/null || return 1
|
|
265
|
+
code=$(curl -s -o "$response" -w '%{http_code}' --max-time 180 \
|
|
261
266
|
-X POST "http://127.0.0.1:$SERVE_PORT/v1/chat/completions" \
|
|
262
|
-
-H 'Content-Type: application/json'
|
|
263
|
-
|
|
264
|
-
[ "$
|
|
267
|
+
-H 'Content-Type: application/json' -H "X-Trillboards-Model-Sha256: $APPROVED_MODEL_SHA256" \
|
|
268
|
+
--data-binary @"$body" 2>/dev/null)
|
|
269
|
+
if [ "$code" = "200" ]; then
|
|
270
|
+
tool_dir="$("${UV:-$LOCAL_BIN/uv}" tool dir 2>/dev/null | tr -d '\r')"
|
|
271
|
+
py="$tool_dir/litert-lm/bin/python"
|
|
272
|
+
for candidate in "$SCRIPT_DIR/verify_model_proof.py" "$SCRIPT_DIR/../litertlm/verify_model_proof.py" "$SCRIPT_DIR/../../litertlm/verify_model_proof.py"; do
|
|
273
|
+
[ -f "$candidate" ] && { verifier="$candidate"; break; }
|
|
274
|
+
done
|
|
275
|
+
[ -n "${verifier:-}" ] && [ -x "$py" ] \
|
|
276
|
+
&& "$py" "$verifier" "$response" "$requested" "$APPROVED_MODEL_SHA256" && valid=0
|
|
277
|
+
fi
|
|
278
|
+
rm -f "$body" "$response" 2>/dev/null || true
|
|
279
|
+
return "$valid"
|
|
265
280
|
}
|
|
281
|
+
verify_litertlm_vision() { verify_litertlm_probe "$MODEL_ID,gpu"; }
|
|
266
282
|
|
|
267
283
|
# The same probe, but asking for NO delegate suffix rather than `,gpu`.
|
|
268
284
|
#
|
|
@@ -273,16 +289,7 @@ verify_litertlm_vision() {
|
|
|
273
289
|
# carries no suffix at all, so a 200 means the box built an engine and ran a real
|
|
274
290
|
# image through it under the config now on disk, which is exactly the claim each
|
|
275
291
|
# rung needs.
|
|
276
|
-
verify_litertlm_plain_inference() {
|
|
277
|
-
_body="$LITERT_DIR/.plain-probe.json"
|
|
278
|
-
printf '{"model":"%s","messages":[{"role":"user","content":[{"type":"text","text":"Reply with the single word: ok"},{"type":"image_url","image_url":{"url":"data:image/jpeg;base64,%s"}}]}],"max_completion_tokens":8,"temperature":0}' \
|
|
279
|
-
"$MODEL_ID" "$LITERTLM_PROBE_JPEG_B64" > "$_body" 2>/dev/null || return 1
|
|
280
|
-
_code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 180 \
|
|
281
|
-
-X POST "http://127.0.0.1:$SERVE_PORT/v1/chat/completions" \
|
|
282
|
-
-H 'Content-Type: application/json' --data-binary @"$_body" 2>/dev/null)
|
|
283
|
-
rm -f "$_body" 2>/dev/null || true
|
|
284
|
-
[ "$_code" = "200" ]
|
|
285
|
-
}
|
|
292
|
+
verify_litertlm_plain_inference() { verify_litertlm_probe "$MODEL_ID"; }
|
|
286
293
|
|
|
287
294
|
local_ai_healthy() {
|
|
288
295
|
curl -fsS --max-time 3 "http://127.0.0.1:$SERVE_PORT/v1/models" 2>/dev/null \
|
|
@@ -362,20 +369,37 @@ fi
|
|
|
362
369
|
|
|
363
370
|
install_litertlm_shim || true
|
|
364
371
|
|
|
365
|
-
# 3. Model
|
|
372
|
+
# 3. Model: a matching name is not a matching artifact. Upgrade from the immutable
|
|
373
|
+
# revision, preserve the old bytes, and verify the installed copy.
|
|
366
374
|
HAVE_MODEL=0
|
|
367
|
-
|
|
368
|
-
|
|
375
|
+
INSTALLED_SHA="$(sha256sum "$STORE_DIR/model.litertlm" 2>/dev/null | awk '{print $1}')"
|
|
376
|
+
if [ "$INSTALLED_SHA" = "$APPROVED_MODEL_SHA256" ]; then
|
|
377
|
+
log "approved model $MODEL_ID already present — skipping import"
|
|
369
378
|
HAVE_MODEL=1
|
|
370
379
|
else
|
|
371
380
|
[ "$ALLOW_MODEL_DOWNLOAD" = "1" ] || fail "local AI model is missing and the network import is disabled"
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
381
|
+
mkdir -p "$STORE_DIR" || fail "model store is unavailable"
|
|
382
|
+
DOWNLOAD="$(mktemp "$STORE_DIR/approved-model.XXXXXX")" || fail "could not create a model download file"
|
|
383
|
+
log "downloading approved $MODEL_ID from immutable revision $MODEL_REVISION"
|
|
384
|
+
if ! curl -fL --retry 3 --output "$DOWNLOAD" "$MODEL_URL" >>"$LOG" 2>&1 \
|
|
385
|
+
|| [ "$(sha256sum "$DOWNLOAD" | awk '{print $1}')" != "$APPROVED_MODEL_SHA256" ]; then
|
|
386
|
+
rm -f "$DOWNLOAD"
|
|
387
|
+
fail "approved model download/checksum failed"
|
|
388
|
+
fi
|
|
389
|
+
if [ -n "$INSTALLED_SHA" ]; then
|
|
390
|
+
ROLLBACK="$STORE_DIR/rollback/$INSTALLED_SHA.litertlm"
|
|
391
|
+
mkdir -p "$STORE_DIR/rollback" || fail "could not preserve previous model"
|
|
392
|
+
[ -f "$ROLLBACK" ] || cp "$STORE_DIR/model.litertlm" "$ROLLBACK" || fail "could not preserve previous model"
|
|
393
|
+
[ "$(sha256sum "$ROLLBACK" | awk '{print $1}')" = "$INSTALLED_SHA" ] || fail "previous model backup verification failed"
|
|
378
394
|
fi
|
|
395
|
+
if ! "$LITERTLM" import "$DOWNLOAD" "$MODEL_ID" >>"$LOG" 2>&1 \
|
|
396
|
+
|| [ "$(sha256sum "$STORE_DIR/model.litertlm" | awk '{print $1}')" != "$APPROVED_MODEL_SHA256" ]; then
|
|
397
|
+
rm -f "$DOWNLOAD"
|
|
398
|
+
fail "approved model import verification failed — the connector will WAIT until this succeeds"
|
|
399
|
+
fi
|
|
400
|
+
rm -f "$DOWNLOAD"
|
|
401
|
+
HAVE_MODEL=1
|
|
402
|
+
log "model imported from verified immutable revision"
|
|
379
403
|
fi
|
|
380
404
|
|
|
381
405
|
# Record the installed version next to the store model so the connector's
|
|
@@ -16,11 +16,11 @@
|
|
|
16
16
|
bin\trillboards-pair.cmd first-run claim-code pairing helper
|
|
17
17
|
lib\edge-sdk\dist\cli.js the connector CLI
|
|
18
18
|
lib\node_modules\... runtime subset (edge-core + edge-platform-rtsp +
|
|
19
|
-
edge-sdk + transitive deps
|
|
20
|
-
local ONNX/
|
|
19
|
+
edge-sdk + transitive deps, including Sharp for
|
|
20
|
+
focused image masks), WITHOUT local ONNX/sqlite.
|
|
21
21
|
|
|
22
|
-
The connector is loaded WITHOUT the heavy ONNX/
|
|
23
|
-
lazy-requires EdgeAgent), so the bundle omits onnxruntime-node/
|
|
22
|
+
The connector is loaded WITHOUT the heavy ONNX/sqlite stack (cli.ts
|
|
23
|
+
lazy-requires EdgeAgent), so the bundle omits onnxruntime-node/
|
|
24
24
|
better-sqlite3 — keeping the inner .exe small. The release workflow signs this
|
|
25
25
|
connector installer, then bundle-windows-installer.ps1 wraps it with the
|
|
26
26
|
pinned Gemma model in one customer-facing executable. The inner installer
|
|
@@ -107,6 +107,7 @@ Push-Location $WorkspaceRoot; npm run build; if ($LASTEXITCODE -ne 0) { Die "edg
|
|
|
107
107
|
Log "Staging edge-sdk dist…"
|
|
108
108
|
New-Item -ItemType Directory -Force -Path "$Payload\lib\edge-sdk" | Out-Null
|
|
109
109
|
Copy-Item -Recurse -Force "$PkgRoot\dist" "$Payload\lib\edge-sdk\dist"
|
|
110
|
+
Copy-Item -Recurse -Force "$PkgRoot\retained" "$Payload\lib\edge-sdk\retained"
|
|
110
111
|
Copy-Item -Force "$PkgRoot\package.json" "$Payload\lib\edge-sdk\package.json"
|
|
111
112
|
|
|
112
113
|
# ─── 3. Runtime node_modules subset (NO heavy native stack) ────────────────────
|
|
@@ -123,9 +124,11 @@ Copy-Item -Force "$PkgRoot\package.json" "$Payload\lib\edge-sdk\package.json"
|
|
|
123
124
|
# the job was killed at its timeout on EVERY release run, so the .exe never
|
|
124
125
|
# published. /MT parallelises the small-file copy; /R:1 /W:1 bound retries so a
|
|
125
126
|
# single transiently-locked file can't trigger robocopy's default 1e6×30s hang.
|
|
126
|
-
Log "Staging runtime node_modules (excluding ONNX/
|
|
127
|
+
Log "Staging runtime node_modules (including focused-image masks; excluding ONNX/sqlite/dev)…"
|
|
127
128
|
New-Item -ItemType Directory -Force -Path "$Payload\lib\node_modules" | Out-Null
|
|
128
|
-
|
|
129
|
+
# Focused retained reviews use Sharp to mask pixels, not just prompt geometry.
|
|
130
|
+
# Keep its platform-native dependencies; detectors still use the local HTTP service.
|
|
131
|
+
$xd = @("onnxruntime-node","better-sqlite3","ffmpeg-static",".cache",".bin","typescript","jest","jest-cli","@jest","ts-jest","@types")
|
|
129
132
|
$rcArgs = @("$WorkspaceRoot\node_modules", "$Payload\lib\node_modules", "/E", "/XJ", "/MT:16", "/R:1", "/W:1", "/NFL","/NDL","/NJH","/NJS","/NP")
|
|
130
133
|
foreach ($d in $xd) { $rcArgs += @("/XD", $d) }
|
|
131
134
|
robocopy @rcArgs | Out-Null
|
|
@@ -141,6 +144,7 @@ foreach ($p in @("edge-core","edge-sensing","edge-ads","edge-federated","edge-cl
|
|
|
141
144
|
if (Test-Path $dest) { Remove-Item -Recurse -Force $dest }
|
|
142
145
|
New-Item -ItemType Directory -Force -Path $dest | Out-Null
|
|
143
146
|
if (Test-Path "$src\dist") { Copy-Item -Recurse -Force "$src\dist" "$dest\dist" }
|
|
147
|
+
if ($p -eq 'edge-sdk') { Copy-Item -Recurse -Force "$src\retained" "$dest\retained" }
|
|
144
148
|
Copy-Item -Force "$src\package.json" "$dest\package.json"
|
|
145
149
|
}
|
|
146
150
|
|
|
@@ -15,8 +15,8 @@ param(
|
|
|
15
15
|
[Parameter(Mandatory = $true)][string]$InnerInstaller,
|
|
16
16
|
[string]$OutputPath = "",
|
|
17
17
|
[string]$ModelPath = "",
|
|
18
|
-
[string]$ModelUrl = "https://huggingface.co/litert-community/gemma-4-E2B-it-litert-lm/resolve/
|
|
19
|
-
[string]$ModelSha256 = "
|
|
18
|
+
[string]$ModelUrl = "https://huggingface.co/litert-community/gemma-4-E2B-it-litert-lm/resolve/84b6978eff6e4eea02825bc2ee4ea48579f13109/gemma-4-E2B-it.litertlm",
|
|
19
|
+
[string]$ModelSha256 = "ab7838cdfc8f77e54d8ca45eadceb20452d9f01e4bfade03e5dce27911b27e42",
|
|
20
20
|
[string]$SevenZipSdkUrl = "https://www.7-zip.org/a/lzma2602.7z",
|
|
21
21
|
[string]$SevenZipSdkSha256 = "2878c85f5f43a4a4e0952b1fd4e5fe097c1c143997a8047c7e1e788892aa9357",
|
|
22
22
|
[string]$IconPath = ""
|
|
@@ -87,7 +87,7 @@ if (-not $sfxModule) { Fail "official 7zSD.sfx installer module was not found" }
|
|
|
87
87
|
# Brand the executable module before appending the config and archive. This
|
|
88
88
|
# keeps the SFX overlay byte-for-byte intact and the final Authenticode signing
|
|
89
89
|
# step covers both the application icon and the customer payload.
|
|
90
|
-
& (Join-Path $Here "set-executable-icon.ps1") -ExecutablePath $sfxModule.FullName -IconPath $IconPath
|
|
90
|
+
& (Join-Path $Here "set-executable-icon.ps1") -ExecutablePath $sfxModule.FullName -IconPath $IconPath -EnsurePerUserExecution
|
|
91
91
|
|
|
92
92
|
$innerName = "TrillboardsEdgeSetup-core.exe"
|
|
93
93
|
Copy-Item -LiteralPath $InnerInstaller -Destination (Join-Path $Stage $innerName) -Force
|
|
@@ -12,11 +12,12 @@
|
|
|
12
12
|
wiring (DEFAULT, runs on EVERY CI event incl. PRs):
|
|
13
13
|
Builds the connector, then runs the cross-platform Node wiring runner
|
|
14
14
|
(scripts/run-local-sense-wiring-e2e.mjs) which drives the REAL built
|
|
15
|
-
`cli.js local-sense` against
|
|
16
|
-
|
|
17
|
-
fixture through
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
`cli.js local-sense` against STUB local VLM/detector services and an
|
|
16
|
+
authenticated retained capability/profile/ingest API, feeding the committed
|
|
17
|
+
fixture through real RTSP/ffmpeg. Checks declared field ownership, pinned
|
|
18
|
+
requests and original evidence/hash/capture time with the genuine retained
|
|
19
|
+
protocol parser/acknowledgement, WITHOUT downloading the real models.
|
|
20
|
+
This is a STUB-MODEL wiring proof — NOT an inference-accuracy proof.
|
|
20
21
|
|
|
21
22
|
bench (workflow_dispatch only):
|
|
22
23
|
Provisions exactly as 'real' does, then runs scripts/bench-local-vlm.mjs
|
|
@@ -56,12 +56,10 @@ function Register-Connector([string]$Root) {
|
|
|
56
56
|
2026-08-19 Windows BOM incident, and it is why this is here rather than left
|
|
57
57
|
to the repair path.
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
update
|
|
62
|
-
|
|
63
|
-
register-detector-task.ps1 already stops the running service, replaces the
|
|
64
|
-
action and starts it again - so this both migrates and restarts it.
|
|
59
|
+
Re-register when the runtime source stamp matches. When it differs, run the
|
|
60
|
+
owned provisioner so a healthy old CPU-only service cannot survive a GPU
|
|
61
|
+
update indefinitely. The provisioner serializes replacement, verifies its
|
|
62
|
+
installed stamp and chooses one CPU/CUDA dependency set. Pairings stay intact.
|
|
65
63
|
|
|
66
64
|
BEST EFFORT, ALWAYS. The detector is a cost optimisation: without it every
|
|
67
65
|
frame goes to the VLM exactly as it does today. It must never be able to fail
|
|
@@ -77,6 +75,47 @@ function Update-DetectorTask([string]$Root) {
|
|
|
77
75
|
# register-task.ps1 states for its own repair.
|
|
78
76
|
$exe = ($task.Actions | Select-Object -First 1).Execute
|
|
79
77
|
if (-not $exe) { return }
|
|
78
|
+
# Older updater array-splatting bound the text '-DetectorPath' positionally
|
|
79
|
+
# as the executable, shifting the real path into --port. Recover ONLY this
|
|
80
|
+
# known shape, under the current user's SID and exact local tool path.
|
|
81
|
+
if ($exe -eq '-DetectorPath') {
|
|
82
|
+
$taskUser = [string]$task.Principal.UserId
|
|
83
|
+
$taskSid = if ($taskUser -match '^S-1-') { $taskUser } else {
|
|
84
|
+
(New-Object System.Security.Principal.NTAccount($taskUser)).Translate([System.Security.Principal.SecurityIdentifier]).Value
|
|
85
|
+
}
|
|
86
|
+
$currentSid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value
|
|
87
|
+
$oldArgs = [string]($task.Actions | Select-Object -First 1).Arguments
|
|
88
|
+
$ownTool = Join-Path $env:USERPROFILE '.local\bin\trillboards-detector.exe'
|
|
89
|
+
if ($taskSid -ne $currentSid -or $oldArgs -notmatch '^serve --host 127\.0\.0\.1 --port (.+?) --log-file "-RamBudgetMb" --ram-budget-mb \d+$') {
|
|
90
|
+
throw 'Cannot verify ownership of the malformed detector task'
|
|
91
|
+
}
|
|
92
|
+
$recovered = $Matches[1]
|
|
93
|
+
if ([IO.Path]::GetFullPath($recovered) -ine [IO.Path]::GetFullPath($ownTool) -or -not (Test-Path -LiteralPath $ownTool)) {
|
|
94
|
+
throw 'Malformed detector task does not identify this user installation'
|
|
95
|
+
}
|
|
96
|
+
$exe = $ownTool
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
$detectorSource = Join-Path $Root 'bin\detector'
|
|
100
|
+
$project = Join-Path $detectorSource 'pyproject.toml'
|
|
101
|
+
$setup = Join-Path $Root 'bin\setup-detector.ps1'
|
|
102
|
+
if ((Test-Path -LiteralPath $project) -and (Test-Path -LiteralPath $setup)) {
|
|
103
|
+
$versionLine = @(Get-Content -LiteralPath $project | Where-Object { $_ -match '^version\s*=\s*"([^"]+)"\s*$' })
|
|
104
|
+
if ($versionLine.Count -ne 1 -or $versionLine[0] -notmatch '^version\s*=\s*"([^"]+)"\s*$') {
|
|
105
|
+
throw 'Cannot resolve the staged detector source version'
|
|
106
|
+
}
|
|
107
|
+
$stagedDetectorVersion = $Matches[1]
|
|
108
|
+
$installedDetectorVersion = ((& $exe --version 2>$null | Out-String).Trim() -split '\s+')[-1]
|
|
109
|
+
if ($LASTEXITCODE -ne 0 -or $installedDetectorVersion -ne $stagedDetectorVersion) {
|
|
110
|
+
Write-Host "Updating the managed detector runtime to $stagedDetectorVersion."
|
|
111
|
+
& $setup -SourcePath $detectorSource
|
|
112
|
+
$verifiedDetectorVersion = ((& $exe --version 2>$null | Out-String).Trim() -split '\s+')[-1]
|
|
113
|
+
if ($LASTEXITCODE -ne 0 -or $verifiedDetectorVersion -ne $stagedDetectorVersion) {
|
|
114
|
+
throw 'Detector runtime update did not verify; pairing preserved, local detector needs repair'
|
|
115
|
+
}
|
|
116
|
+
return # the owned provisioner already registered and restarted the task
|
|
117
|
+
}
|
|
118
|
+
}
|
|
80
119
|
|
|
81
120
|
$register = Join-Path $Root "bin\register-detector-task.ps1"
|
|
82
121
|
if (-not (Test-Path -LiteralPath $register)) { return }
|
|
@@ -93,12 +132,16 @@ function Update-DetectorTask([string]$Root) {
|
|
|
93
132
|
if ($LASTEXITCODE -eq 0 -and $derived -match '^\d+$') { $budget = $derived }
|
|
94
133
|
}
|
|
95
134
|
|
|
96
|
-
|
|
97
|
-
|
|
135
|
+
# A PowerShell SCRIPT takes named parameters from a hashtable. An array of
|
|
136
|
+
# flag strings is positional; it creates Execute='-DetectorPath' while
|
|
137
|
+
# reporting registration success. Native powershell.exe argument arrays
|
|
138
|
+
# elsewhere are a different boundary and must not be changed with this.
|
|
139
|
+
$registerArgs = @{ DetectorPath = $exe }
|
|
140
|
+
if ($budget) { $registerArgs.RamBudgetMb = $budget }
|
|
98
141
|
& $register @registerArgs | Out-Null
|
|
99
142
|
Write-Host "Detector task migrated$(if ($budget) { " (RAM ceiling $budget MiB)" })."
|
|
100
143
|
} catch {
|
|
101
|
-
Write-Warning "Could not migrate the detector task: $($_.Exception.Message).
|
|
144
|
+
Write-Warning "Could not migrate the detector task: $($_.Exception.Message). Detector-backed profiles need the local detector repaired; camera pairing is preserved."
|
|
102
145
|
}
|
|
103
146
|
}
|
|
104
147
|
|