@trillboards/edge-sdk 0.2.138 → 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/TrillboardsSetupApp.m +131 -3
- package/deploy/macos/build-macos-pkg.sh +13 -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/setup-guidance.json +112 -0
- package/deploy/systemd/setup-detector.sh +2 -2
- package/deploy/systemd/setup-litertlm.sh +51 -27
- package/deploy/windows/build-windows.ps1 +11 -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/deploy/windows/trillboards-setup.ps1 +101 -2
- 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
|
@@ -40,6 +40,14 @@ static BOOL acquireSetupInstanceLock(void) {
|
|
|
40
40
|
|
|
41
41
|
@interface TrillboardsSetupDelegate : NSObject <NSApplicationDelegate, NSWindowDelegate, WKNavigationDelegate, WKUIDelegate>
|
|
42
42
|
@property(nonatomic, strong) NSWindow *window;
|
|
43
|
+
@property(nonatomic, strong) NSDictionary *guidanceCatalog;
|
|
44
|
+
@property(nonatomic, strong) NSView *guidancePanel;
|
|
45
|
+
@property(nonatomic, strong) NSTextField *guidanceTitle;
|
|
46
|
+
@property(nonatomic, strong) NSTextField *guidanceBody;
|
|
47
|
+
@property(nonatomic, strong) NSButton *guidanceToggle;
|
|
48
|
+
@property(nonatomic, strong) NSButton *guidanceRestore;
|
|
49
|
+
@property(nonatomic, strong) NSMutableSet<NSString *> *dismissedGuidance;
|
|
50
|
+
|
|
43
51
|
@property(nonatomic, strong) NSFileHandle *setupOutput;
|
|
44
52
|
@property(nonatomic, strong) NSMutableData *setupOutputBuffer;
|
|
45
53
|
@property(nonatomic, strong) NSWindow *cameraWindow;
|
|
@@ -327,14 +335,14 @@ static BOOL acquireSetupInstanceLock(void) {
|
|
|
327
335
|
|
|
328
336
|
- (void)buildWindow {
|
|
329
337
|
self.window = [[NSWindow alloc]
|
|
330
|
-
initWithContentRect:NSMakeRect(0, 0, 760,
|
|
338
|
+
initWithContentRect:NSMakeRect(0, 0, 760, 700)
|
|
331
339
|
styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable
|
|
332
340
|
backing:NSBackingStoreBuffered
|
|
333
341
|
defer:NO];
|
|
334
342
|
self.window.title = @"Sense Anything Connector";
|
|
335
343
|
self.window.delegate = self;
|
|
336
|
-
self.window.minSize = NSMakeSize(760,
|
|
337
|
-
self.window.maxSize = NSMakeSize(760,
|
|
344
|
+
self.window.minSize = NSMakeSize(760, 700);
|
|
345
|
+
self.window.maxSize = NSMakeSize(760, 700);
|
|
338
346
|
[self.window center];
|
|
339
347
|
|
|
340
348
|
NSView *root = self.window.contentView;
|
|
@@ -536,10 +544,85 @@ static BOOL acquireSetupInstanceLock(void) {
|
|
|
536
544
|
[self.chooseAccountPanel addSubview:self.chooseAccountSubmit];
|
|
537
545
|
[root addSubview:self.chooseAccountPanel];
|
|
538
546
|
|
|
547
|
+
// Reserve a separate help region; never cover native forms or recovery actions.
|
|
548
|
+
for (NSView *view in root.subviews) {
|
|
549
|
+
NSRect frame = view.frame;
|
|
550
|
+
frame.origin.y += 160;
|
|
551
|
+
view.frame = frame;
|
|
552
|
+
}
|
|
553
|
+
NSView *guideSidebar = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 190, 160)];
|
|
554
|
+
guideSidebar.wantsLayer = YES;
|
|
555
|
+
guideSidebar.layer.backgroundColor = NSColor.controlBackgroundColor.CGColor;
|
|
556
|
+
[root addSubview:guideSidebar];
|
|
557
|
+
NSString *guidePath = [NSBundle.mainBundle pathForResource:@"setup-guidance" ofType:@"json"];
|
|
558
|
+
NSData *guideData = guidePath ? [NSData dataWithContentsOfFile:guidePath] : nil;
|
|
559
|
+
id catalog = guideData ? [NSJSONSerialization JSONObjectWithData:guideData options:0 error:nil] : nil;
|
|
560
|
+
self.guidanceCatalog = [catalog isKindOfClass:NSDictionary.class] ? catalog : @{};
|
|
561
|
+
self.dismissedGuidance = [NSMutableSet set];
|
|
562
|
+
self.guidanceToggle = [NSButton checkboxWithTitle:@"Guiding tips" target:self action:@selector(toggleGuidance)];
|
|
563
|
+
self.guidanceToggle.frame = NSMakeRect(24, 118, 150, 24);
|
|
564
|
+
self.guidanceToggle.state = [NSUserDefaults.standardUserDefaults boolForKey:@"setupGuidingTipsDisabled"] ? NSControlStateValueOff : NSControlStateValueOn;
|
|
565
|
+
[root addSubview:self.guidanceToggle];
|
|
566
|
+
self.guidanceRestore = [self panelButton:@"Show this tip" frame:NSMakeRect(20, 80, 150, 28) action:@selector(restoreGuidance)];
|
|
567
|
+
[root addSubview:self.guidanceRestore];
|
|
568
|
+
self.guidancePanel = [[NSView alloc] initWithFrame:NSMakeRect(236, 12, 465, 145)];
|
|
569
|
+
self.guidancePanel.wantsLayer = YES;
|
|
570
|
+
self.guidancePanel.layer.backgroundColor = [NSColor colorWithCalibratedRed:1 green:0.96 blue:0.91 alpha:1].CGColor;
|
|
571
|
+
self.guidancePanel.layer.borderColor = [NSColor colorWithCalibratedRed:0.93 green:0.65 blue:0.40 alpha:1].CGColor;
|
|
572
|
+
self.guidancePanel.layer.borderWidth = 1;
|
|
573
|
+
self.guidancePanel.layer.cornerRadius = 10;
|
|
574
|
+
self.guidanceTitle = [self label:@"" size:13 weight:NSFontWeightSemibold frame:NSMakeRect(14, 94, 405, 40)];
|
|
575
|
+
self.guidanceBody = [self label:@"" size:12 weight:NSFontWeightRegular frame:NSMakeRect(14, 35, 435, 60)];
|
|
576
|
+
self.guidanceTitle.textColor = [NSColor colorWithCalibratedRed:0.28 green:0.20 blue:0.14 alpha:1];
|
|
577
|
+
self.guidanceBody.textColor = [NSColor colorWithCalibratedRed:0.38 green:0.29 blue:0.23 alpha:1];
|
|
578
|
+
[self.guidancePanel addSubview:self.guidanceTitle];
|
|
579
|
+
[self.guidancePanel addSubview:self.guidanceBody];
|
|
580
|
+
NSButton *dismiss = [self panelButton:@"×" frame:NSMakeRect(424, 110, 30, 26) action:@selector(dismissGuidance)];
|
|
581
|
+
dismiss.accessibilityLabel = @"Dismiss this tip";
|
|
582
|
+
[self.guidancePanel addSubview:dismiss];
|
|
583
|
+
[self.guidancePanel addSubview:[self panelButton:@"More help" frame:NSMakeRect(8, 5, 110, 26) action:@selector(showGuidanceDetail)]];
|
|
584
|
+
[root addSubview:self.guidancePanel];
|
|
585
|
+
|
|
539
586
|
self.onboardingPanels = @[self.accountPanel, self.termsPanel, self.planPanel, self.chooseAccountPanel];
|
|
540
587
|
[self showOnboardingPanel:nil];
|
|
541
588
|
}
|
|
542
589
|
|
|
590
|
+
- (void)renderGuidance {
|
|
591
|
+
NSDictionary *guide = self.guidanceCatalog[self.lastStage];
|
|
592
|
+
BOOL enabled = self.guidanceToggle.state == NSControlStateValueOn;
|
|
593
|
+
BOOL dismissed = [self.dismissedGuidance containsObject:self.lastStage];
|
|
594
|
+
self.guidancePanel.hidden = !enabled || dismissed || ![guide isKindOfClass:NSDictionary.class];
|
|
595
|
+
self.guidanceRestore.hidden = !enabled || !dismissed;
|
|
596
|
+
self.guidanceTitle.stringValue = [guide[@"title"] isKindOfClass:NSString.class] ? guide[@"title"] : @"";
|
|
597
|
+
self.guidanceBody.stringValue = [guide[@"body"] isKindOfClass:NSString.class] ? guide[@"body"] : @"";
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
- (void)toggleGuidance {
|
|
601
|
+
[NSUserDefaults.standardUserDefaults setBool:self.guidanceToggle.state != NSControlStateValueOn forKey:@"setupGuidingTipsDisabled"];
|
|
602
|
+
[self.dismissedGuidance removeAllObjects];
|
|
603
|
+
[self renderGuidance];
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
- (void)dismissGuidance {
|
|
607
|
+
[self.dismissedGuidance addObject:self.lastStage];
|
|
608
|
+
[self renderGuidance];
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
- (void)restoreGuidance {
|
|
612
|
+
[self.dismissedGuidance removeObject:self.lastStage];
|
|
613
|
+
[self renderGuidance];
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
- (void)showGuidanceDetail {
|
|
617
|
+
NSDictionary *guide = self.guidanceCatalog[self.lastStage];
|
|
618
|
+
if (![guide[@"detail"] isKindOfClass:NSString.class]) return;
|
|
619
|
+
NSAlert *alert = [[NSAlert alloc] init];
|
|
620
|
+
alert.messageText = self.guidanceTitle.stringValue;
|
|
621
|
+
alert.informativeText = guide[@"detail"];
|
|
622
|
+
[alert addButtonWithTitle:@"Got it"];
|
|
623
|
+
[alert beginSheetModalForWindow:self.window completionHandler:nil];
|
|
624
|
+
}
|
|
625
|
+
|
|
543
626
|
- (void)showOnboardingPanel:(NSView *)panel {
|
|
544
627
|
for (NSView *candidate in self.onboardingPanels ?: @[]) candidate.hidden = candidate != panel;
|
|
545
628
|
BOOL showStatus = panel == nil;
|
|
@@ -663,6 +746,7 @@ static BOOL acquireSetupInstanceLock(void) {
|
|
|
663
746
|
- (void)showWelcome {
|
|
664
747
|
[self showOnboardingPanel:nil];
|
|
665
748
|
self.lastStage = @"welcome";
|
|
749
|
+
[self renderGuidance];
|
|
666
750
|
self.headline.stringValue = @"Set up this computer for CCTV";
|
|
667
751
|
self.detail.stringValue = @"Connect your Sense Anything account, find your recorder, and verify every camera before screens are created.";
|
|
668
752
|
self.statusValue.stringValue = @"About 5 minutes";
|
|
@@ -678,6 +762,7 @@ static BOOL acquireSetupInstanceLock(void) {
|
|
|
678
762
|
- (void)showLocalError:(NSString *)title message:(NSString *)message {
|
|
679
763
|
[self showOnboardingPanel:nil];
|
|
680
764
|
self.lastStage = @"error";
|
|
765
|
+
[self renderGuidance];
|
|
681
766
|
self.headline.stringValue = title;
|
|
682
767
|
self.detail.stringValue = message;
|
|
683
768
|
self.statusValue.stringValue = @"Setup stopped";
|
|
@@ -763,6 +848,7 @@ static BOOL acquireSetupInstanceLock(void) {
|
|
|
763
848
|
}
|
|
764
849
|
self.requiredUpdateURL = downloadURL;
|
|
765
850
|
self.lastStage = @"update_required";
|
|
851
|
+
[self renderGuidance];
|
|
766
852
|
self.headline.stringValue = @"Update required before setup can continue";
|
|
767
853
|
self.detail.stringValue = @"Install the latest Sense Anything Connector, then reopen it to finish camera setup.";
|
|
768
854
|
self.statusValue.stringValue = [NSString stringWithFormat:@"Version %@", availableText];
|
|
@@ -835,6 +921,7 @@ static BOOL acquireSetupInstanceLock(void) {
|
|
|
835
921
|
if (![files fileExistsAtPath:logPath]) [files createFileAtPath:logPath contents:nil attributes:nil];
|
|
836
922
|
|
|
837
923
|
self.lastStage = @"starting";
|
|
924
|
+
[self renderGuidance];
|
|
838
925
|
self.setupStarted = YES;
|
|
839
926
|
self.headline.stringValue = @"Starting camera setup";
|
|
840
927
|
self.detail.stringValue = @"Connecting securely to Sense Anything.";
|
|
@@ -868,6 +955,7 @@ static BOOL acquireSetupInstanceLock(void) {
|
|
|
868
955
|
if (!stage) return;
|
|
869
956
|
NSString *previousStage = self.lastStage;
|
|
870
957
|
self.lastStage = stage;
|
|
958
|
+
[self renderGuidance];
|
|
871
959
|
if ([state[@"portalUrl"] isKindOfClass:NSString.class]) self.portalURL = state[@"portalUrl"];
|
|
872
960
|
if ([state[@"title"] isKindOfClass:NSString.class]) self.headline.stringValue = state[@"title"];
|
|
873
961
|
if ([state[@"message"] isKindOfClass:NSString.class]) self.detail.stringValue = state[@"message"];
|
|
@@ -1168,6 +1256,43 @@ static BOOL acquireSetupInstanceLock(void) {
|
|
|
1168
1256
|
|
|
1169
1257
|
@end
|
|
1170
1258
|
|
|
1259
|
+
static int runGuidanceSmokeTest(void) {
|
|
1260
|
+
[NSApplication sharedApplication];
|
|
1261
|
+
TrillboardsSetupDelegate *delegate = [[TrillboardsSetupDelegate alloc] init];
|
|
1262
|
+
[delegate buildWindow];
|
|
1263
|
+
if (delegate.guidanceCatalog.count == 0) return 1;
|
|
1264
|
+
delegate.guidanceToggle.state = NSControlStateValueOn;
|
|
1265
|
+
for (NSString *stage in delegate.guidanceCatalog) {
|
|
1266
|
+
delegate.lastStage = stage;
|
|
1267
|
+
[delegate renderGuidance];
|
|
1268
|
+
if (delegate.guidancePanel.hidden || !delegate.guidanceTitle.stringValue.length) return 2;
|
|
1269
|
+
for (NSTextField *field in @[delegate.guidanceTitle, delegate.guidanceBody]) {
|
|
1270
|
+
NSRect textBounds = [field.attributedStringValue boundingRectWithSize:NSMakeSize(field.frame.size.width - 4, CGFLOAT_MAX)
|
|
1271
|
+
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading];
|
|
1272
|
+
if (textBounds.size.height > field.frame.size.height) {
|
|
1273
|
+
fprintf(stderr, "Guidance text clipped at %s\n", stage.UTF8String);
|
|
1274
|
+
return 3;
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
[delegate dismissGuidance];
|
|
1278
|
+
if (!delegate.guidancePanel.hidden || delegate.guidanceRestore.hidden) return 4;
|
|
1279
|
+
[delegate restoreGuidance];
|
|
1280
|
+
if (delegate.guidancePanel.hidden) return 5;
|
|
1281
|
+
}
|
|
1282
|
+
delegate.lastStage = @"onboarding_terms";
|
|
1283
|
+
delegate.termsCheck.state = NSControlStateValueOff;
|
|
1284
|
+
delegate.termsSubmit.enabled = NO;
|
|
1285
|
+
delegate.guidanceToggle.state = NSControlStateValueOff;
|
|
1286
|
+
[delegate renderGuidance];
|
|
1287
|
+
if (!delegate.guidancePanel.hidden || delegate.termsSubmit.enabled) return 6;
|
|
1288
|
+
if (NSIntersectsRect(delegate.guidancePanel.frame, delegate.accountPanel.frame) ||
|
|
1289
|
+
NSIntersectsRect(delegate.guidancePanel.frame, delegate.actionButton.frame)) return 7;
|
|
1290
|
+
fprintf(stdout, "Native guidance stages, dismissal, text bounds and consent isolation passed.\n");
|
|
1291
|
+
fflush(stdout);
|
|
1292
|
+
[delegate.window close];
|
|
1293
|
+
return 0;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1171
1296
|
static int runCancellationSmokeTest(void) {
|
|
1172
1297
|
NSString *pidPath = [NSTemporaryDirectory()
|
|
1173
1298
|
stringByAppendingPathComponent:[NSString stringWithFormat:@"trillboards-setup-child-%@.pid", NSUUID.UUID.UUIDString]];
|
|
@@ -1288,6 +1413,9 @@ int main(int argc, const char *argv[]) {
|
|
|
1288
1413
|
if (argc == 3 && [[NSString stringWithUTF8String:argv[1]] isEqualToString:@"--smoke-test-ui-pipe"]) {
|
|
1289
1414
|
return runNativePipeSmokeTest([NSString stringWithUTF8String:argv[2]]);
|
|
1290
1415
|
}
|
|
1416
|
+
if (argc == 2 && [[NSString stringWithUTF8String:argv[1]] isEqualToString:@"--smoke-test-guidance"]) {
|
|
1417
|
+
return runGuidanceSmokeTest();
|
|
1418
|
+
}
|
|
1291
1419
|
if (argc == 2 && [[NSString stringWithUTF8String:argv[1]] isEqualToString:@"--smoke-test-cancel"]) {
|
|
1292
1420
|
return runCancellationSmokeTest();
|
|
1293
1421
|
}
|
|
@@ -16,12 +16,11 @@
|
|
|
16
16
|
# lib/edge-sdk/dist/cli.js the connector CLI
|
|
17
17
|
# lib/node_modules/... the runtime subset (edge-core +
|
|
18
18
|
# edge-platform-rtsp + edge-sdk + their
|
|
19
|
-
# transitive deps),
|
|
20
|
-
# ONNX
|
|
19
|
+
# transitive deps), including Sharp for scoped
|
|
20
|
+
# evidence masks, without the ONNX stack.
|
|
21
21
|
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
# better-sqlite3 — keeping the pkg small.
|
|
22
|
+
# The local CCTV path does not load EdgeAgent's ONNX stack. The bundle omits
|
|
23
|
+
# onnxruntime-node/better-sqlite3, but retains Sharp and its native pixel codec.
|
|
25
24
|
#
|
|
26
25
|
# Install (no sudo, no admin password):
|
|
27
26
|
# installer -pkg <pkg> -target CurrentUserHomeDirectory
|
|
@@ -131,8 +130,8 @@ FFMPEG_SHA256_X64="${FFMPEG_SHA256_X64:-cfe20936c83ecf5d68e424b87e8cc45b24dd6be8
|
|
|
131
130
|
# at a verified local cache; otherwise the release build fetches the pinned URL.
|
|
132
131
|
# Override GEMMA_MODEL_URL / GEMMA_MODEL_SHA256 only when upstream is re-pinned.
|
|
133
132
|
GEMMA_MODEL_ID="gemma-4-E2B-it.litertlm"
|
|
134
|
-
GEMMA_MODEL_URL="${GEMMA_MODEL_URL:-https://huggingface.co/litert-community/gemma-4-E2B-it-litert-lm/resolve/
|
|
135
|
-
GEMMA_MODEL_SHA256="${GEMMA_MODEL_SHA256:-
|
|
133
|
+
GEMMA_MODEL_URL="${GEMMA_MODEL_URL:-https://huggingface.co/litert-community/gemma-4-E2B-it-litert-lm/resolve/84b6978eff6e4eea02825bc2ee4ea48579f13109/gemma-4-E2B-it.litertlm}"
|
|
134
|
+
GEMMA_MODEL_SHA256="${GEMMA_MODEL_SHA256:-ab7838cdfc8f77e54d8ca45eadceb20452d9f01e4bfade03e5dce27911b27e42}"
|
|
136
135
|
GEMMA_MODEL_FILE="${GEMMA_MODEL_FILE:-}"
|
|
137
136
|
BUNDLE_MODEL=1
|
|
138
137
|
|
|
@@ -207,6 +206,7 @@ log "Building all edge workspaces (tsc, dependency order)…"
|
|
|
207
206
|
log "Staging edge-sdk dist…"
|
|
208
207
|
mkdir -p "$PAYLOAD/lib/edge-sdk"
|
|
209
208
|
cp -R "$PKG_ROOT/dist" "$PAYLOAD/lib/edge-sdk/dist"
|
|
209
|
+
cp -R "$PKG_ROOT/retained" "$PAYLOAD/lib/edge-sdk/retained"
|
|
210
210
|
cp "$PKG_ROOT/package.json" "$PAYLOAD/lib/edge-sdk/package.json"
|
|
211
211
|
|
|
212
212
|
# ─── 3. Lay down the runtime node_modules subset (NO heavy native stack) ──────
|
|
@@ -214,12 +214,10 @@ cp "$PKG_ROOT/package.json" "$PAYLOAD/lib/edge-sdk/package.json"
|
|
|
214
214
|
# closure) and EXCLUDE the heavy/native/dev packages. cloud-sense + pair never
|
|
215
215
|
# require them (EdgeAgent → edge-sensing → onnxruntime-node is lazy now), so
|
|
216
216
|
# their absence is fine and keeps the pkg ~50 MB instead of ~270 MB.
|
|
217
|
-
log "Staging runtime node_modules (excluding ONNX/
|
|
217
|
+
log "Staging runtime node_modules (including focused-image masks; excluding ONNX/sqlite/dev)…"
|
|
218
218
|
mkdir -p "$PAYLOAD/lib/node_modules"
|
|
219
219
|
rsync -a \
|
|
220
220
|
--exclude 'onnxruntime-node' \
|
|
221
|
-
--exclude 'sharp' \
|
|
222
|
-
--exclude '@img' \
|
|
223
221
|
--exclude 'better-sqlite3' \
|
|
224
222
|
--exclude 'ffmpeg-static' \
|
|
225
223
|
--exclude '.cache' \
|
|
@@ -244,6 +242,7 @@ for p in edge-core edge-sensing edge-ads edge-federated edge-cloud \
|
|
|
244
242
|
rm -rf "$dest"
|
|
245
243
|
mkdir -p "$dest"
|
|
246
244
|
[ -d "$src/dist" ] && cp -R "$src/dist" "$dest/dist"
|
|
245
|
+
[ "$p" != "edge-sdk" ] || cp -R "$src/retained" "$dest/retained"
|
|
247
246
|
cp "$src/package.json" "$dest/package.json"
|
|
248
247
|
done
|
|
249
248
|
|
|
@@ -424,6 +423,7 @@ cp "$PKG_ROOT/deploy/macos/scripts/setup-litertlm.sh" "$PAYLOAD/share/setup-lite
|
|
|
424
423
|
# looks first. It is copied into the litert-lm tool venv's site-packages at
|
|
425
424
|
# provisioning time; nothing else reads it, and no upstream file is modified.
|
|
426
425
|
cp "$PKG_ROOT/deploy/litertlm/trillboards_litertlm_shim.py" "$PAYLOAD/share/trillboards_litertlm_shim.py"
|
|
426
|
+
cp "$PKG_ROOT/deploy/litertlm/verify_model_proof.py" "$PAYLOAD/share/verify_model_proof.py"
|
|
427
427
|
cp "$PKG_ROOT/deploy/macos/scripts/ensure-litertlm.sh" "$PAYLOAD/share/ensure-litertlm.sh"
|
|
428
428
|
cp "$PKG_ROOT/deploy/macos/apply-update.sh" "$PAYLOAD/share/apply-update.sh"
|
|
429
429
|
|
|
@@ -490,6 +490,7 @@ SETUP_APP_BIN="$SETUP_APP_MACOS/Sense Anything Connector"
|
|
|
490
490
|
mkdir -p "$SETUP_APP_MACOS" "$SETUP_APP_RESOURCES"
|
|
491
491
|
cp "$PKG_ROOT/deploy/branding/SenseAnythingConnector.icns" \
|
|
492
492
|
"$SETUP_APP_RESOURCES/SenseAnythingConnector.icns"
|
|
493
|
+
cp "$PKG_ROOT/deploy/setup-guidance.json" "$SETUP_APP_RESOURCES/setup-guidance.json"
|
|
493
494
|
log "Building native Sense Anything Connector.app…"
|
|
494
495
|
if [ "$UNIVERSAL" -eq 1 ]; then
|
|
495
496
|
clang -fobjc-arc -Wall -Wextra -Werror -O2 -mmacosx-version-min=12.0 -arch arm64 -arch x86_64 -framework Cocoa -framework WebKit \
|
|
@@ -536,6 +537,8 @@ cat > "$SETUP_APP/Contents/Info.plist" <<PLIST
|
|
|
536
537
|
</plist>
|
|
537
538
|
PLIST
|
|
538
539
|
plutil -lint "$SETUP_APP/Contents/Info.plist" >/dev/null || die "native setup app Info.plist is invalid"
|
|
540
|
+
"$SETUP_APP_BIN" --smoke-test-guidance \
|
|
541
|
+
|| die "native setup guidance smoke test failed"
|
|
539
542
|
file "$SETUP_APP_BIN" | grep -q 'Mach-O' || die "native setup app executable is not Mach-O"
|
|
540
543
|
log "Native setup app staged: $SETUP_APP"
|
|
541
544
|
|
|
@@ -146,7 +146,7 @@ fi
|
|
|
146
146
|
# nothing — an exact pin cannot resolve to different bytes.
|
|
147
147
|
SOURCE="$(find_detector_source)" || { log "ERROR: detector package source not found next to this script"; exit 0; }
|
|
148
148
|
log "installing/repairing trillboards-detector from $SOURCE with uv-managed Python $DETECTOR_PYTHON"
|
|
149
|
-
if ! "$UV" tool install --python "$DETECTOR_PYTHON" --managed-python --force --refresh-package trillboards-detector "$SOURCE" >>"$LOG" 2>&1; then
|
|
149
|
+
if ! "$UV" tool install --python "$DETECTOR_PYTHON" --managed-python --force --refresh-package trillboards-detector "${SOURCE}[cpu]" >>"$LOG" 2>&1; then
|
|
150
150
|
log "ERROR: trillboards-detector install failed; the cascade stays off (the VLM still runs every frame)"
|
|
151
151
|
exit 0
|
|
152
152
|
fi
|
|
@@ -182,7 +182,7 @@ elif [ "$HAVE_VERSION" != "$WANT_VERSION" ]; then
|
|
|
182
182
|
# asking it again the same way is not a repair.
|
|
183
183
|
log "WARN: the installed detector reports '$HAVE_VERSION' but the staged package is '$WANT_VERSION' - reinstalling from scratch"
|
|
184
184
|
"$UV" tool uninstall trillboards-detector >>"$LOG" 2>&1 || true
|
|
185
|
-
"$UV" tool install --python "$DETECTOR_PYTHON" --managed-python --force --refresh-package trillboards-detector "$SOURCE" >>"$LOG" 2>&1 || true
|
|
185
|
+
"$UV" tool install --python "$DETECTOR_PYTHON" --managed-python --force --refresh-package trillboards-detector "${SOURCE}[cpu]" >>"$LOG" 2>&1 || true
|
|
186
186
|
DETECTOR="$LOCAL_BIN/trillboards-detector"
|
|
187
187
|
[ -x "$DETECTOR" ] || DETECTOR="$(command -v trillboards-detector || echo "$DETECTOR")"
|
|
188
188
|
HAVE_VERSION="$(installed_detector_version)"
|
|
@@ -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
|
|