diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 67eddc097..d758207cd 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -1,17 +1,9 @@ name: CodeQL - on: - push: - branches: [main] + workflow_call: schedule: - # Run weekly on Mondays at 06:00 UTC to catch new CVE patterns - cron: '0 6 * * 1' workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - permissions: actions: read contents: read @@ -19,56 +11,106 @@ permissions: jobs: analyze: - name: CodeQL Analysis + name: CodeQL (${{ matrix.language }}) runs-on: ubuntu-latest container: debian:sid-slim - timeout-minutes: 45 - + timeout-minutes: 60 strategy: fail-fast: false matrix: - language: ['c-cpp'] - + include: + - language: c-cpp + scanner: codeql-cpp + build-mode: manual + suite: codeql/cpp-queries:codeql-suites/cpp-security-extended.qls + - language: javascript-typescript + scanner: codeql-javascript-typescript + build-mode: none + suite: codeql/javascript-queries:codeql-suites/javascript-security-extended.qls + defaults: + run: + shell: bash + env: + REPORT_DIR: build/analysis/${{ matrix.scanner }} + SCANNER_ID: ${{ matrix.scanner }} + LANGUAGE: ${{ matrix.language }} + QUERY_SUITE: ${{ matrix.suite }} + SPEC_FILE: build/codeql-spec.json steps: - name: Install build dependencies run: | apt-get update - apt-get install -y \ - git build-essential cmake pkg-config \ + apt-get install -y git python3 build-essential cmake pkg-config \ libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libavfilter-dev \ libsqlite3-dev libuv1-dev libllhttp-dev libcurl4-openssl-dev \ libcjson-dev libmbedtls-dev libmosquitto-dev - - - name: Checkout - uses: actions/checkout@v6 + - uses: actions/checkout@v6 with: submodules: false - - - name: Checkout FAAC submodule - run: | - # Keep optional go2rtc/LiteRT sources out of these builds. - git config --global --add safe.directory "$GITHUB_WORKSPACE" - git submodule update --init --depth 1 third_party/faac - - name: Initialize CodeQL + id: init uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} - # Use extended queries for more thorough security analysis + build-mode: ${{ matrix.build-mode }} + db-location: ${{ github.workspace }}/build/eng-codeql queries: security-extended - - - name: Build for CodeQL + - name: Build explicit native analysis profile + if: matrix.language == 'c-cpp' run: | - mkdir -p build && cd build - cmake .. \ - -DCMAKE_BUILD_TYPE=Release \ - -DENABLE_SOD=OFF \ - -DENABLE_GO2RTC=OFF \ - -DBUILD_TESTS=OFF - make -j$(nproc) - - - name: Perform CodeQL Analysis + git config --global --add safe.directory "$GITHUB_WORKSPACE" + git submodule update --init --depth 1 third_party/faac + cmake -S . -B build/codeql-native -DCMAKE_BUILD_TYPE=Release \ + -DENABLE_SOD=OFF -DENABLE_GO2RTC=OFF -DENABLE_LITERT=OFF \ + -DENABLE_SENTRY=OFF -DENABLE_SSL=OFF -DENABLE_MQTT=ON -DBUILD_TESTS=OFF + cmake --build build/codeql-native --parallel 2 + - name: Finalize extracted databases without running queries + id: finalize uses: github/codeql-action/analyze@v4 with: - category: "/language:${{ matrix.language }}" - + skip-queries: true + upload: never + - name: Generate CodeQL scanner specification + env: + CODEQL_BIN: ${{ steps.init.outputs.codeql-path }} + CODEQL_DATABASES: ${{ steps.finalize.outputs.db-locations }} + run: | + python3 - <<'PY' + import json, os + from pathlib import Path + databases = json.loads(os.environ['CODEQL_DATABASES']) + if len(databases) != 1: raise ValueError('Expected exactly one extracted language database') + database = Path(next(iter(databases.values()))).resolve() + archive = (database / 'src.zip').relative_to(Path.cwd()).as_posix() + report = os.environ['SCANNER_ID'] + '.sarif' + spec = {'scanner_id':os.environ['SCANNER_ID'], 'version_command':[os.environ['CODEQL_BIN'],'version'], + 'commands':[{'argv':[os.environ['CODEQL_BIN'],'database','analyze',str(database),os.environ['QUERY_SUITE'], + '--download','--format=sarif-latest','--output=' + str(Path.cwd() / os.environ['REPORT_DIR'] / report), + '--sarif-category=/language:' + os.environ['LANGUAGE'],'--sarif-add-baseline-file-info'], + 'reports':[report], 'timeout_seconds':2700}], + 'scope':{'kind':'codeql-source-archive','archive':archive}} + if os.environ['LANGUAGE'] == 'c-cpp': + spec['faac'] = { + 'provenance':'build/codeql-native/_deps/lightnvr-faac/provenance.json', + 'compile_commands':'build/codeql-native/compile_commands.json'} + Path(os.environ['SPEC_FILE']).parent.mkdir(parents=True,exist_ok=True) + Path(os.environ['SPEC_FILE']).write_text(json.dumps(spec)) + PY + - name: Run CodeQL queries with actual exit and archive provenance + run: python3 scripts/quality/scanner_run.py --spec "$SPEC_FILE" --reports "$REPORT_DIR" --expected-revision "$GITHUB_SHA" + - name: Check scoped CodeQL findings + if: always() + run: python3 scripts/quality/findings_lane.py --scanner "$SCANNER_ID" --reports "$REPORT_DIR" --expected-revision "$GITHUB_SHA" --json-out "$REPORT_DIR/lane-result.json" + - name: Upload available SARIF to code scanning + if: always() && hashFiles(format('{0}/*.sarif', env.REPORT_DIR)) != '' + uses: github/codeql-action/upload-sarif@v4 + with: + sarif_file: ${{ env.REPORT_DIR }}/${{ matrix.scanner }}.sarif + category: /language:${{ matrix.language }} + - uses: actions/upload-artifact@v7 + if: always() + with: + name: analysis-${{ matrix.scanner }} + path: ${{ env.REPORT_DIR }} + if-no-files-found: error + retention-days: 30 diff --git a/.github/workflows/container-scan.yml b/.github/workflows/container-scan.yml index e6b782df5..4eba63368 100644 --- a/.github/workflows/container-scan.yml +++ b/.github/workflows/container-scan.yml @@ -1,32 +1,25 @@ name: Container Security Scan - on: + workflow_call: push: - branches: [main] - tags: - - '*.*.*' + tags: ['*.*.*'] workflow_dispatch: - permissions: contents: read security-events: write - jobs: trivy: name: Trivy Container Scan runs-on: ubuntu-latest - timeout-minutes: 30 - + timeout-minutes: 45 + env: + REPORT_DIR: build/analysis/trivy steps: - - name: Checkout - uses: actions/checkout@v6 + - uses: actions/checkout@v6 with: submodules: recursive - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 - - - name: Build Docker image for scanning + - uses: docker/setup-buildx-action@v4 + - name: Build local image for analysis uses: docker/build-push-action@v7 with: context: . @@ -36,46 +29,43 @@ jobs: tags: lightnvr:scan build-args: | GIT_COMMIT=${{ github.sha }} - - - name: Install Trivy + - name: Install Trivy and fetch vulnerability database run: | sudo apt-get update sudo apt-get install -y wget apt-transport-https gnupg wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | gpg --dearmor | sudo tee /usr/share/keyrings/trivy.gpg > /dev/null - echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] https://aquasecurity.github.io/trivy-repo/deb generic main" | sudo tee /etc/apt/sources.list.d/trivy.list + echo 'deb [signed-by=/usr/share/keyrings/trivy.gpg] https://aquasecurity.github.io/trivy-repo/deb generic main' | sudo tee /etc/apt/sources.list.d/trivy.list sudo apt-get update sudo apt-get install -y trivy - - - name: Run Trivy vulnerability scanner (SARIF) - id: trivy-sarif + trivy image --download-db-only + - name: Capture Trivy report, image identity and database metadata run: | - trivy image \ - --format sarif \ - --output trivy-results.sarif \ - --severity CRITICAL,HIGH,MEDIUM \ - --exit-code 0 \ - lightnvr:scan - - - name: Upload Trivy SARIF to GitHub Security tab + python3 - <<'PY' + import json, os + from pathlib import Path + spec = {'scanner_id':'trivy-container', 'version_command':['trivy','version','--format','json'], 'version_format':'trivy-json', + 'commands':[{'argv':['trivy','image','--scanners','vuln','--format','sarif', + '--output',str(Path.cwd() / os.environ['REPORT_DIR'] / 'trivy-results.sarif'), + '--skip-db-update','--severity','CRITICAL,HIGH,MEDIUM','--exit-code','2','lightnvr:scan'], + 'reports':['trivy-results.sarif'],'accepted_exit_codes':[0,2],'timeout_seconds':1800}], + 'scope':{'kind':'image','report':'trivy-results.sarif'}} + Path('build').mkdir(exist_ok=True) + Path('build/trivy-spec.json').write_text(json.dumps(spec)) + PY + python3 scripts/quality/scanner_run.py --spec build/trivy-spec.json --reports "$REPORT_DIR" --expected-revision "$GITHUB_SHA" + - name: Check scoped container findings + if: always() + run: python3 scripts/quality/findings_lane.py --scanner trivy-container --reports "$REPORT_DIR" --expected-revision "$GITHUB_SHA" --json-out "$REPORT_DIR/lane-result.json" + - name: Upload available SARIF to code scanning uses: github/codeql-action/upload-sarif@v4 - if: always() && steps.trivy-sarif.outcome == 'success' + if: always() && hashFiles(format('{0}/trivy-results.sarif', env.REPORT_DIR)) != '' with: - sarif_file: 'trivy-results.sarif' - category: 'container-scanning' - - - name: Run Trivy (table output for logs) - if: always() && steps.trivy-sarif.outcome == 'success' - run: | - trivy image \ - --format table \ - --severity CRITICAL,HIGH \ - --exit-code 0 \ - lightnvr:scan - - - name: Upload Trivy report - uses: actions/upload-artifact@v7 - if: always() && steps.trivy-sarif.outcome == 'success' + sarif_file: ${{ env.REPORT_DIR }}/trivy-results.sarif + category: container-scanning + - uses: actions/upload-artifact@v7 + if: always() with: - name: trivy-scan-results - path: trivy-results.sarif + name: analysis-trivy + path: ${{ env.REPORT_DIR }} + if-no-files-found: error retention-days: 30 diff --git a/.github/workflows/engineering-foundation.yml b/.github/workflows/engineering-foundation.yml new file mode 100644 index 000000000..ccea392ee --- /dev/null +++ b/.github/workflows/engineering-foundation.yml @@ -0,0 +1,268 @@ +# All PRs run every job so required checks cannot remain pending due to path filters. +name: Engineering Foundation +on: + pull_request: + branches: + - main + push: + branches: + - main + workflow_dispatch: {} +permissions: + contents: read +jobs: + types: + name: Foundation types + runs-on: ubuntu-latest + timeout-minutes: 20 + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 + with: + node-version-file: .nvmrc + cache: npm + cache-dependency-path: |- + package-lock.json + web/package-lock.json + - name: Install locked dependencies + run: |- + npm ci + npm ci --prefix web + - name: Run checks + run: |- + mkdir -p build/eng-artifacts/foundation-types + { + npm --prefix web run typecheck + npm run typecheck:tests + } 2>&1 | tee build/eng-artifacts/foundation-types/check.log + - name: Preserve diagnostics + if: always() + uses: actions/upload-artifact@v7 + with: + name: foundation-types + path: build/eng-artifacts/foundation-types + retention-days: 14 + if-no-files-found: warn + lint: + name: Foundation lint + runs-on: ubuntu-latest + timeout-minutes: 20 + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 + with: + node-version-file: .nvmrc + cache: npm + cache-dependency-path: |- + package-lock.json + web/package-lock.json + - name: Install locked dependencies + run: |- + npm ci + npm ci --prefix web + - name: Run checks + run: |- + mkdir -p build/eng-artifacts/foundation-lint + { + npm --prefix web run lint + } 2>&1 | tee build/eng-artifacts/foundation-lint/check.log + - name: Preserve diagnostics + if: always() + uses: actions/upload-artifact@v7 + with: + name: foundation-lint + path: build/eng-artifacts/foundation-lint + retention-days: 14 + if-no-files-found: warn + unit: + name: Foundation unit + runs-on: ubuntu-latest + timeout-minutes: 20 + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 + with: + node-version-file: .nvmrc + cache: npm + cache-dependency-path: |- + package-lock.json + web/package-lock.json + - name: Install locked dependencies + run: |- + npm ci + npm ci --prefix web + - name: Run checks + run: |- + mkdir -p build/eng-artifacts/foundation-unit + { + npm --prefix web test -- --runInBand + } 2>&1 | tee build/eng-artifacts/foundation-unit/check.log + - name: Preserve diagnostics + if: always() + uses: actions/upload-artifact@v7 + with: + name: foundation-unit + path: build/eng-artifacts/foundation-unit + retention-days: 14 + if-no-files-found: warn + schema: + name: Foundation schema + runs-on: ubuntu-latest + timeout-minutes: 20 + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 + with: + node-version-file: .nvmrc + cache: npm + cache-dependency-path: |- + package-lock.json + web/package-lock.json + - name: Install locked dependencies + run: |- + npm ci + npm ci --prefix web + - name: Run checks + run: |- + mkdir -p build/eng-artifacts/foundation-schema + { + node scripts/quality/contracts.mjs --check + } 2>&1 | tee build/eng-artifacts/foundation-schema/check.log + - name: Preserve diagnostics + if: always() + uses: actions/upload-artifact@v7 + with: + name: foundation-schema + path: build/eng-artifacts/foundation-schema + retention-days: 14 + if-no-files-found: warn + tooling: + name: Foundation tooling + runs-on: ubuntu-latest + timeout-minutes: 20 + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 + with: + node-version-file: .nvmrc + cache: npm + cache-dependency-path: |- + package-lock.json + web/package-lock.json + - name: Install locked dependencies + run: |- + npm ci + npm ci --prefix web + - name: Run checks + run: |- + mkdir -p build/eng-artifacts/foundation-tooling + { + node --test tests/quality/*.test.mjs + python3 -m unittest discover -s tests/quality -p 'test_*.py' + python3 tests/operational/soak/test_soak.py + python3 tests/operational/lifecycle/test_stale_guard_migration.py + python3 tests/unit/test_embedded_migration_generator.py + python3 scripts/generate_embedded_migrations.py --check db/migrations include/database/db_embedded_migrations.h + python3 quality/evidence/T7-check.py --require-integrated + } 2>&1 | tee build/eng-artifacts/foundation-tooling/check.log + - name: Preserve diagnostics + if: always() + uses: actions/upload-artifact@v7 + with: + name: foundation-tooling + path: build/eng-artifacts/foundation-tooling + retention-days: 14 + if-no-files-found: warn + assets-default: + name: Foundation assets default + runs-on: ubuntu-latest + timeout-minutes: 20 + defaults: + run: + shell: bash + env: + LIGHTNVR_WEB_LEGACY: "true" + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 + with: + node-version-file: .nvmrc + cache: npm + cache-dependency-path: |- + package-lock.json + web/package-lock.json + - name: Install locked dependencies + run: |- + npm ci + npm ci --prefix web + - name: Run checks + run: >- + mkdir -p build/eng-artifacts/foundation-assets-default + + { + npm --prefix web run build + node scripts/quality/assets.mjs --report > build/eng-artifacts/foundation-assets-default/assets.json + node scripts/quality/assets.mjs --check --profile default + } 2>&1 | tee build/eng-artifacts/foundation-assets-default/check.log + - name: Preserve diagnostics + if: always() + uses: actions/upload-artifact@v7 + with: + name: foundation-assets-default + path: build/eng-artifacts/foundation-assets-default + retention-days: 14 + if-no-files-found: warn + assets-modern: + name: Foundation assets modern + runs-on: ubuntu-latest + timeout-minutes: 20 + defaults: + run: + shell: bash + env: + LIGHTNVR_WEB_LEGACY: "false" + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 + with: + node-version-file: .nvmrc + cache: npm + cache-dependency-path: |- + package-lock.json + web/package-lock.json + - name: Install locked dependencies + run: |- + npm ci + npm ci --prefix web + - name: Run checks + run: >- + mkdir -p build/eng-artifacts/foundation-assets-modern + + { + npm --prefix web run build + node scripts/quality/assets.mjs --report > build/eng-artifacts/foundation-assets-modern/assets.json + node scripts/quality/assets.mjs --check --profile modern + } 2>&1 | tee build/eng-artifacts/foundation-assets-modern/check.log + - name: Preserve diagnostics + if: always() + uses: actions/upload-artifact@v7 + with: + name: foundation-assets-modern + path: build/eng-artifacts/foundation-assets-modern + retention-days: 14 + if-no-files-found: warn diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index f4f825289..1a97721da 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -82,6 +82,33 @@ jobs: test_stream_frame_rate \ test_recording_video_params \ test_mp4_segment_boundaries \ + test_mp4_writer_watchdog \ + test_mp4_segment_timestamps \ + test_mp4_faststart \ + test_mp4_path_collision \ + test_mp4_pressure_reconnect \ + test_t7_16 \ + test_t7_28 \ + test_t7_53 \ + test_t7_54 \ + test_t7_25 \ + test_t7_26 \ + test_t7_55 \ + test_t7_25_retention \ + test_t7_58 \ + test_t7_27 \ + test_t7_59 \ + test_t7_59_start_failure \ + test_t7_23 \ + test_t7_2_migrations \ + test_incomplete_media_validation \ + test_stale_recording_prune \ + test_stream_state_disposal \ + test_delete_stream_launch_failure \ + test_put_stream_launch_failure \ + test_storage_recording_faults \ + test_storage_writer_backpressure \ + test_storage_deletion_ledger \ test_storage_retention_sqlite \ test_storage_manager_retention \ test_storage_target_pressure_cleanup \ @@ -144,8 +171,8 @@ jobs: - name: Run Unity tests run: | cd build - ctest --output-on-failure -V \ - -R "test_storage_pressure$|test_storage_pressure_extended|test_detection_result_structures|test_stream_frame_rate|test_recording_video_params|test_mp4_segment_boundaries|test_storage_retention_sqlite|test_storage_manager_retention|test_storage_target_pressure_cleanup|test_storage_migration|test_storage_archive_s3|test_storage_archive_operator|test_authorization|test_api_handlers_recordings_playback|test_db_storage_targets|test_db_storage_policies|test_api_handlers_storage_targets|test_api_handlers_storage_policies|test_config|test_logger|test_strings|test_memory|test_request_response|test_shutdown_coordinator|test_detection_config|test_detection_model_motion|test_db_streams|test_db_camera_favorites|test_api_handlers_camera_favorites|test_db_backup|test_mp4_failed_open_memory|test_db_recordings_extended|test_db_detections|test_db_zones|test_db_events|test_db_auth|test_db_transactions|test_db_maintenance|test_db_query_builder|test_logger_json|test_batch_delete_progress|test_db_recordings_sync|test_httpd_utils|test_zone_filter|test_onvif_soap_fault|test_faac_encoder|test_audio_transcoder|test_mp4_audio_recovery|test_mp4_forced_reconnect|test_mp4_recovery_watchdog|test_audio_voice_enhancement|test_audio_voice_filter_faults|test_udt_recording|test_detection_current_frame|test_detection_io_recovery|test_hls_cleanup|test_stream_manager|test_stream_state|test_packet_buffer|test_timestamp_manager|test_motion_trigger_parse|test_motion_max_area|test_url_utils" + ctest --output-on-failure --no-tests=error -V \ + -R "test_storage_pressure$|test_storage_pressure_extended|test_detection_result_structures|test_stream_frame_rate|test_recording_video_params|test_mp4_segment_boundaries|test_storage_retention_sqlite|test_storage_manager_retention|test_storage_target_pressure_cleanup|test_storage_migration|test_storage_archive_s3|test_storage_archive_operator|test_authorization|test_api_handlers_recordings_playback|test_db_storage_targets|test_db_storage_policies|test_api_handlers_storage_targets|test_api_handlers_storage_policies|test_config|test_logger|test_strings|test_memory|test_request_response|test_shutdown_coordinator|test_detection_config|test_detection_model_motion|test_db_streams|test_db_camera_favorites|test_api_handlers_camera_favorites|test_db_backup|test_mp4_failed_open_memory|test_db_recordings_extended|test_db_detections|test_db_zones|test_db_events|test_db_auth|test_db_transactions|test_db_maintenance|test_db_query_builder|test_logger_json|test_batch_delete_progress|test_db_recordings_sync|test_httpd_utils|test_zone_filter|test_onvif_soap_fault|test_faac_encoder|test_audio_transcoder|test_mp4_audio_recovery|test_mp4_forced_reconnect|test_mp4_recovery_watchdog|test_audio_voice_enhancement|test_audio_voice_filter_faults|test_udt_recording|test_detection_current_frame|test_detection_io_recovery|test_hls_cleanup|test_stream_manager|test_stream_state|test_packet_buffer|test_timestamp_manager|test_motion_trigger_parse|test_motion_max_area|test_url_utils|test_mp4_writer_watchdog|test_mp4_segment_timestamps|test_mp4_faststart|test_stream_state_disposal|test_delete_stream_launch_failure|test_put_stream_launch_failure|test_storage_recording_faults|test_storage_writer_backpressure|test_storage_deletion_ledger|test_mp4_path_collision|test_incomplete_media_validation|test_mp4_pressure_reconnect|test_t7_16|test_t7_28$|test_t7_53$|test_t7_54$|test_t7_25$|test_t7_26$|test_t7_55$|test_t7_25_retention$|test_t7_58$|test_t7_27$|test_t7_59$|test_t7_59_start_failure$|test_t7_23$|test_t7_2_migrations|test_stale_recording_prune" - name: Generate coverage report if: always() @@ -327,14 +354,13 @@ jobs: run: | npm ci npx playwright install --with-deps chromium - LIGHTNVR_TEST_BIN="$GITHUB_WORKSPACE/build/health-ci/bin/lightnvr" \ - LIGHTNVR_TEST_CONFIG="$GITHUB_WORKSPACE/config/lightnvr-health-test.ini" \ - LIGHTNVR_SKIP_GO2RTC=1 \ - npx playwright test \ - tests/integration/specs/system-health.api.spec.ts \ - tests/integration/specs/system-health-mqtt.api.spec.ts \ - tests/integration/specs/system-health.ui.spec.ts \ - tests/integration/specs/system-health-settings.ui.spec.ts + node scripts/quality/run-integration.mjs \ + --task ci-health --project all --config-profile health \ + --binary "$GITHUB_WORKSPACE/build/health-ci/bin/lightnvr" \ + --file tests/integration/specs/system-health.api.spec.ts \ + --file tests/integration/specs/system-health-mqtt.api.spec.ts \ + --file tests/integration/specs/system-health.ui.spec.ts \ + --file tests/integration/specs/system-health-settings.ui.spec.ts - name: Generate system health coverage report if: always() @@ -368,12 +394,12 @@ jobs: - name: Upload health test artifacts uses: actions/upload-artifact@v7 - if: failure() + if: always() with: name: system-health-test-results path: | build/health-ci/Testing/ - playwright-report/ + build/eng-artifacts/ci-health/ test-results/ retention-days: 7 @@ -448,6 +474,10 @@ jobs: run: | cd build make -j$(nproc) \ + test_t7_2_buffers \ + test_t7_2_files \ + test_t7_2_settings \ + test_t7_24 \ test_yaml_validate \ test_yaml_redact \ test_db_system_settings \ @@ -464,8 +494,8 @@ jobs: - name: Run go2rtc-related tests run: | cd build - ctest --output-on-failure -V \ - -R "test_yaml_validate|test_yaml_redact|test_db_system_settings|test_go2rtc_process_detection|test_go2rtc_proxy_thread|test_go2rtc_proxy_routing|test_go2rtc_process_config_generation|test_go2rtc_substream_registration|test_go2rtc_live_transcode|test_go2rtc_binary_detection|test_go2rtc_two_config_merge|test_ffmpeg_guard" + ctest --output-on-failure --no-tests=error -V \ + -R "test_t7_2_buffers$|test_t7_2_files$|test_t7_2_settings$|test_t7_24$|test_yaml_validate|test_yaml_redact|test_db_system_settings|test_go2rtc_process_detection|test_go2rtc_proxy_thread|test_go2rtc_proxy_routing|test_go2rtc_process_config_generation|test_go2rtc_substream_registration|test_go2rtc_live_transcode|test_go2rtc_binary_detection|test_go2rtc_two_config_merge|test_ffmpeg_guard" - name: Generate coverage report if: always() @@ -529,6 +559,7 @@ jobs: build-essential \ cmake \ pkg-config \ + python3 \ libavcodec-dev \ libavformat-dev \ libavutil-dev \ @@ -606,94 +637,25 @@ jobs: - name: Run Playwright UI tests run: | - # Clean any existing playwright reports to ensure fresh state - rm -rf playwright-report test-results - mkdir -p playwright-report test-results - - # Pre-flight checks before running tests - echo "=== Pre-flight checks ===" - echo "Checking if lightNVR binary exists..." - ls -la build/bin/lightnvr || echo "lightNVR binary NOT found" - echo "" - echo "Checking if go2rtc binary exists..." - ls -la go2rtc/go2rtc || echo "go2rtc binary NOT found" - echo "" - echo "Checking if web/dist exists..." - ls -la web/dist/ | head -5 || echo "web/dist NOT found" - echo "" - echo "Checking if config file exists..." - ls -la config/lightnvr-test.ini || echo "Config file NOT found" - echo "" - - echo "" - echo "=== Running Playwright shard ${{ matrix.shard }}/2 ===" - - # Stream output into the job log while preserving a copy for the - # failure artifact. This also leaves useful progress visible if a - # runner is cancelled externally. - RESULT=0 - set +e - npx playwright test --project=ui --shard=${{ matrix.shard }}/2 2>&1 | tee playwright-output.log - RESULT=${PIPESTATUS[0]} - set -e - - if [ $RESULT -ne 0 ]; then - echo "" - echo "=== Playwright tests failed ===" - echo "" - echo "Checking if lightNVR is still running..." - pgrep -a lightnvr || echo "lightNVR is NOT running" - pgrep -a go2rtc || echo "go2rtc is NOT running" - echo "" - echo "Checking lightNVR logs..." - if [ -f /tmp/lightnvr-test/lightnvr.log ]; then - echo "=== Last 200 lines of lightnvr.log ===" - tail -200 /tmp/lightnvr-test/lightnvr.log - else - echo "No log file found at /tmp/lightnvr-test/lightnvr.log" - fi - echo "" - echo "Checking for core dumps..." - ls -la /tmp/core* 2>/dev/null || echo "No core dumps found" - echo "" - exit $RESULT - fi - shell: bash + node scripts/quality/run-integration.mjs \ + --task ci-ui-${{ matrix.shard }} --project ui \ + --binary "$GITHUB_WORKSPACE/build/bin/lightnvr" \ + --shard ${{ matrix.shard }}/2 env: CI: true - - name: Upload Playwright report + - name: Upload Playwright artifacts uses: actions/upload-artifact@v7 if: always() with: name: playwright-report-${{ matrix.shard }} - path: playwright-report/ + path: build/eng-artifacts/ci-ui-${{ matrix.shard }}/ retention-days: 30 - - name: Upload test screenshots + - name: Upload legacy explicit-path screenshots uses: actions/upload-artifact@v7 if: failure() with: name: test-screenshots-${{ matrix.shard }} path: test-results/ retention-days: 7 - - - name: Upload lightNVR logs - uses: actions/upload-artifact@v7 - if: failure() - with: - name: lightnvr-logs-${{ matrix.shard }} - path: | - /tmp/lightnvr-test/ - playwright-output.log - retention-days: 7 - - - name: Cleanup - if: always() - run: | - # Stop any remaining processes - pkill -f lightnvr || true - pkill -f go2rtc || true - - # Clean up test artifacts - rm -rf /tmp/lightnvr-test diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index d56351f62..10957d2ac 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -1,20 +1,22 @@ name: Static Analysis - on: push: branches: [main] pull_request: branches: [main] workflow_dispatch: - permissions: contents: read + actions: read + security-events: write jobs: - web-jsx-a11y: - name: Web JSX accessibility lint + web: + name: ESLint findings runs-on: ubuntu-latest timeout-minutes: 15 + env: + REPORT_DIR: build/analysis/eslint steps: - uses: actions/checkout@v6 - uses: actions/setup-node@v6 @@ -22,151 +24,223 @@ jobs: node-version-file: .nvmrc cache: npm cache-dependency-path: web/package-lock.json - - name: Install web dependencies - run: npm ci --prefix web - - name: Report JSX accessibility findings + - run: npm ci --prefix web + - name: Capture ESLint JSON and execution provenance run: | - npm run lint --prefix web - echo 'The existing WCAG audit findings are warnings. Promote each rule to an error as its finding is fixed.' >> "$GITHUB_STEP_SUMMARY" + python3 - <<'PY' + import json, os + from pathlib import Path + spec = {'scanner_id':'eslint-web', 'version_command':['web/node_modules/.bin/eslint','--version'], + 'commands':[{'argv':['./node_modules/.bin/eslint','js','--format','json'], 'cwd':'web', + 'stdout_report':'eslint.json','reports':['eslint.json'],'accepted_exit_codes':[0,1]}], + 'scope':{'kind':'eslint-json','report':'eslint.json'}} + Path('build').mkdir(exist_ok=True) + Path('build/eslint-spec.json').write_text(json.dumps(spec)) + PY + python3 scripts/quality/scanner_run.py --spec build/eslint-spec.json --reports "$REPORT_DIR" --expected-revision "$GITHUB_SHA" + - name: Check scoped ESLint findings + if: always() + run: python3 scripts/quality/findings_lane.py --scanner eslint-web --reports "$REPORT_DIR" --expected-revision "$GITHUB_SHA" --json-out "$REPORT_DIR/lane-result.json" + - uses: actions/upload-artifact@v7 + if: always() + with: + name: analysis-eslint + path: ${{ env.REPORT_DIR }} + if-no-files-found: error + retention-days: 30 - clang-tidy: - name: clang-tidy + dependencies: + name: npm audit (${{ matrix.project }}) runs-on: ubuntu-latest - container: debian:sid-slim - timeout-minutes: 30 + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + project: [root, web] + env: + PROJECT: ${{ matrix.project }} + REPORT_DIR: build/analysis/npm-${{ matrix.project }} + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 + with: + node-version-file: .nvmrc + - name: Audit production and complete dependency graphs + run: | + python3 - <<'PY' + import json, os + from pathlib import Path + project = os.environ['PROJECT'] + commands = [] + for variant in ('all','prod'): + report = f'{project}-audit-{variant}.json' + command = {'argv':['npm','audit','--package-lock-only','--json'] + (['--omit=dev'] if variant == 'prod' else []), + 'reports':[report], 'stdout_report':report, 'accepted_exit_codes':[0,1]} + if project == 'web': command['cwd'] = 'web' + commands.append(command) + spec = {'scanner_id':f'npm-audit-{project}', 'version_command':['npm','--version'], + 'commands':commands, 'scope':{'kind':'npm-lockfiles'}} + Path('build').mkdir(exist_ok=True) + Path('build/npm-spec.json').write_text(json.dumps(spec)) + PY + python3 scripts/quality/scanner_run.py --spec build/npm-spec.json --reports "$REPORT_DIR" --expected-revision "$GITHUB_SHA" + - name: Check scoped dependency findings + if: always() + run: python3 scripts/quality/findings_lane.py --scanner "npm-audit-$PROJECT" --reports "$REPORT_DIR" --expected-revision "$GITHUB_SHA" --json-out "$REPORT_DIR/lane-result.json" + - uses: actions/upload-artifact@v7 + if: always() + with: + name: analysis-npm-${{ matrix.project }} + path: ${{ env.REPORT_DIR }} + if-no-files-found: error + retention-days: 30 + native: + name: Native analysis (${{ matrix.scanner }}) + runs-on: ubuntu-latest + container: debian:sid-slim + timeout-minutes: 45 + strategy: + fail-fast: false + matrix: + scanner: [clang-tidy, cppcheck] + defaults: + run: + shell: bash + env: + SCANNER_ID: ${{ matrix.scanner }} + REPORT_DIR: build/analysis/${{ matrix.scanner }} steps: - - name: Install dependencies + - name: Install native analysis dependencies run: | apt-get update - apt-get install -y \ - git build-essential cmake pkg-config clang-tidy \ + apt-get install -y git python3 build-essential cmake pkg-config clang-tidy cppcheck \ libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libavfilter-dev \ libsqlite3-dev libuv1-dev libllhttp-dev libcurl4-openssl-dev \ libcjson-dev libmbedtls-dev libmosquitto-dev - - - name: Checkout - uses: actions/checkout@v6 + - uses: actions/checkout@v6 with: submodules: false - - - name: Checkout FAAC submodule + - name: Configure explicit analysis profile run: | - # Keep optional go2rtc/LiteRT sources out of these builds. git config --global --add safe.directory "$GITHUB_WORKSPACE" git submodule update --init --depth 1 third_party/faac - - - name: Generate compile_commands.json - run: | - mkdir -p build && cd build - cmake .. \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ - -DENABLE_SOD=OFF \ - -DENABLE_GO2RTC=OFF \ - -DBUILD_TESTS=OFF - # Copy to project root for clang-tidy - cp compile_commands.json .. - - - name: Run clang-tidy + cmake -S . -B build/analysis-native -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DENABLE_SOD=OFF -DENABLE_GO2RTC=OFF \ + -DENABLE_LITERT=OFF -DENABLE_SENTRY=OFF -DENABLE_SSL=OFF -DENABLE_MQTT=ON -DBUILD_TESTS=OFF + - name: Analyze with captured exit and source scope run: | - echo "::group::clang-tidy version" - clang-tidy --version - echo "::endgroup::" - - # Find all C source files under src/, excluding third-party code - find src/ -name '*.c' -not -path '*/sod/*' -not -path '*/ezxml/*' | sort > /tmp/source_files.txt - echo "Scanning $(wc -l < /tmp/source_files.txt) source files..." - - # Run clang-tidy (non-blocking: always exit 0) - set +e - clang-tidy \ - -p compile_commands.json \ - $(cat /tmp/source_files.txt) \ - --quiet \ - 2>&1 | tee clang-tidy-report.txt - set -e - - # Count warnings - WARNINGS=$(grep -c "warning:" clang-tidy-report.txt || true) - ERRORS=$(grep -c "error:" clang-tidy-report.txt || true) - echo "## clang-tidy Results" >> $GITHUB_STEP_SUMMARY - echo "- **Warnings**: ${WARNINGS}" >> $GITHUB_STEP_SUMMARY - echo "- **Errors**: ${ERRORS}" >> $GITHUB_STEP_SUMMARY - - - name: Upload clang-tidy report - uses: actions/upload-artifact@v7 + python3 - <<'PY' + import json, os, sys + from pathlib import Path + sys.path.insert(0, 'scripts/quality') + from scanner_run import native_analysis_inputs + root = Path.cwd() + sid = os.environ['SCANNER_ID'] + report_dir = os.environ['REPORT_DIR'] + faac = {'provenance':'build/analysis-native/_deps/lightnvr-faac/provenance.json', + 'compile_commands':'build/analysis-native/compile_commands.json'} + files = native_analysis_inputs(root, faac, sid) + if sid == 'clang-tidy': + report = 'clang-tidy-report.txt' + command = {'argv':['clang-tidy','-p','build/analysis-native','--quiet'], + 'stdout_report':report, 'stderr_to_stdout':True, 'reports':[report], + 'append_scope_files':True, 'timeout_seconds':2100} + scope = {'kind':'invocation-files','files':files} + else: + # Keep every source matched by the former */src/* filter and add + # actual patched FAAC units, retaining each compiler invocation. + entries = json.loads(Path(faac['compile_commands']).read_text()) + selected = set(files) + filtered = [row for row in entries + if Path(row['file']).resolve().relative_to(root).as_posix() in selected] + project = 'build/analysis-native/compile_commands.cppcheck.json' + Path(project).write_text(json.dumps(filtered, indent=2) + '\n') + faac['analysis_compile_commands'] = project + report = 'cppcheck-report.xml' + command = {'argv':['cppcheck','--project=' + project, + '--enable=all','--suppress=missingIncludeSystem', + '--suppress=unusedFunction','--inline-suppr','--error-exitcode=2','--xml','--xml-version=2', + '--output-file=' + str(root / report_dir / report)], + 'reports':[report], 'accepted_exit_codes':[0,2], 'timeout_seconds':2100} + scope = {'kind':'cppcheck-progress','report':'logs/cppcheck-0.stdout.log'} + spec = {'scanner_id':sid,'version_command':[sid,'--version'], + 'commands':[command],'scope':scope,'faac':faac} + Path('build/native-spec.json').write_text(json.dumps(spec)) + PY + python3 scripts/quality/scanner_run.py --spec build/native-spec.json --reports "$REPORT_DIR" --expected-revision "$GITHUB_SHA" + - name: Check scoped native findings + if: always() + run: python3 scripts/quality/findings_lane.py --scanner "$SCANNER_ID" --reports "$REPORT_DIR" --expected-revision "$GITHUB_SHA" --json-out "$REPORT_DIR/lane-result.json" + - uses: actions/upload-artifact@v7 if: always() with: - name: clang-tidy-report - path: clang-tidy-report.txt + name: analysis-${{ matrix.scanner }} + path: ${{ env.REPORT_DIR }} + if-no-files-found: error retention-days: 30 - cppcheck: - name: cppcheck - runs-on: ubuntu-latest - timeout-minutes: 30 + codeql: + uses: ./.github/workflows/codeql-analysis.yml + container: + uses: ./.github/workflows/container-scan.yml + analysis-coverage: + name: Analysis coverage and findings + runs-on: ubuntu-latest + timeout-minutes: 15 + needs: [web, dependencies, native, codeql, container] + if: always() + env: + INCOMING_DIR: build/analysis-incoming + REPORT_DIR: build/analysis-combined steps: - - name: Checkout - uses: actions/checkout@v6 + - uses: actions/checkout@v6 + - name: Download only this workflow run's evidence + uses: actions/download-artifact@v8 with: - submodules: false - - - name: Install dependencies + pattern: analysis-* + path: ${{ env.INCOMING_DIR }} + - name: Merge same-run scanner manifests run: | - sudo apt-get update - sudo apt-get install -y cppcheck - - - name: Run cppcheck - run: | - echo "::group::cppcheck version" - cppcheck --version - echo "::endgroup::" - - # Run cppcheck with XML output (non-blocking: --error-exitcode=0) - cppcheck \ - --enable=all \ - --suppress=missingIncludeSystem \ - --suppress=unusedFunction \ - --inline-suppr \ - --error-exitcode=0 \ - --xml \ - --xml-version=2 \ - -I include/ \ - src/ \ - 2> cppcheck-report.xml - - # Also generate a human-readable report - cppcheck \ - --enable=all \ - --suppress=missingIncludeSystem \ - --suppress=unusedFunction \ - --inline-suppr \ - --error-exitcode=0 \ - --template='{file}:{line}: {severity}: {message} [{id}]' \ - -I include/ \ - src/ \ - 2> cppcheck-report.txt - - # Summary - ERRORS=$(grep -c 'severity="error"' cppcheck-report.xml || true) - WARNINGS=$(grep -c 'severity="warning"' cppcheck-report.xml || true) - STYLE=$(grep -c 'severity="style"' cppcheck-report.xml || true) - PERF=$(grep -c 'severity="performance"' cppcheck-report.xml || true) - echo "## cppcheck Results" >> $GITHUB_STEP_SUMMARY - echo "- **Errors**: ${ERRORS}" >> $GITHUB_STEP_SUMMARY - echo "- **Warnings**: ${WARNINGS}" >> $GITHUB_STEP_SUMMARY - echo "- **Style**: ${STYLE}" >> $GITHUB_STEP_SUMMARY - echo "- **Performance**: ${PERF}" >> $GITHUB_STEP_SUMMARY - - - name: Upload cppcheck reports - uses: actions/upload-artifact@v7 + python3 - <<'PY' + import json, os, shutil + from pathlib import Path, PurePosixPath + incoming, output = Path(os.environ['INCOMING_DIR']), Path(os.environ['REPORT_DIR']) + output.mkdir(parents=True, exist_ok=False) + merged = {'schema_version':1,'revision':os.environ['GITHUB_SHA'],'scanners':{}} + def portable(value): + p = PurePosixPath(value) + if not value or p.is_absolute() or '\\' in value or any(x in ('.','..','') for x in value.split('/')): + raise ValueError('Unsafe artifact evidence path') + return value + for artifact in sorted(incoming.iterdir() if incoming.exists() else []): + if artifact.is_symlink() or not artifact.is_dir(): raise ValueError('Invalid artifact directory') + if any(p.is_symlink() for p in artifact.rglob('*')): raise ValueError('Symlink in artifact') + manifest_path = artifact / 'run-manifest.json' + if not manifest_path.exists(): continue # Full checker reports missing scanner; never an empty pass. + manifest = json.loads(manifest_path.read_text()) + if manifest.get('schema_version') != 1 or manifest.get('revision') != merged['revision']: + raise ValueError('Artifact has wrong schema or revision') + prefix = portable(artifact.name) + '/' + for sid, entry in manifest['scanners'].items(): + if sid in merged['scanners']: raise ValueError('Duplicate scanner evidence') + entry['reports'] = [prefix + portable(p) for p in entry['reports']] + entry['report_sha256'] = {prefix + portable(p): h for p,h in entry['report_sha256'].items()} + scope = entry.get('scope') or {} + if 'evidence_sha256' in scope: + scope['evidence_sha256'] = {prefix + portable(p): h for p,h in scope['evidence_sha256'].items()} + merged['scanners'][sid] = entry + shutil.copytree(artifact, output / artifact.name) + (output / 'run-manifest.json').write_text(json.dumps(merged,indent=2) + '\n') + PY + - name: Enforce full expected coverage and findings policy + if: always() + run: python3 scripts/quality/findings.py --check --reports "$REPORT_DIR" --expected-revision "$GITHUB_SHA" --json-out "$REPORT_DIR/policy-result.json" + - uses: actions/upload-artifact@v7 if: always() with: - name: cppcheck-reports - path: | - cppcheck-report.xml - cppcheck-report.txt + name: findings-policy-evidence + path: ${{ env.REPORT_DIR }} + if-no-files-found: error retention-days: 30 - diff --git a/CMakeLists.txt b/CMakeLists.txt index dd398c9a9..efa20460f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -471,30 +471,33 @@ set(INIH_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/external/inih/ini.c") add_library(inih_lib STATIC ${INIH_SOURCES}) target_include_directories(inih_lib INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/external/inih") +# Compile a verified build-local copy; never modify the pinned submodule. +set(FAAC_PREPARED_ROOT "${CMAKE_BINARY_DIR}/_deps/lightnvr-faac") +set(FAAC_PREPARE_ARGS + "-DFAAC_SUPERPROJECT_DIR=${CMAKE_CURRENT_SOURCE_DIR}" + "-DFAAC_UPSTREAM_DIR=${CMAKE_CURRENT_SOURCE_DIR}/third_party/faac" + "-DFAAC_BUILD_DIR=${FAAC_PREPARED_ROOT}" + "-DFAAC_PATCH_DIR=${CMAKE_CURRENT_SOURCE_DIR}/patches/faac" + "-DFAAC_COMPILER=${CMAKE_C_COMPILER} ${CMAKE_C_COMPILER_ID} ${CMAKE_C_COMPILER_VERSION}" + "-DFAAC_BUILD_PROFILE=${CMAKE_BUILD_TYPE} ${CMAKE_C_FLAGS}" + -P "${CMAKE_CURRENT_SOURCE_DIR}/cmake/PrepareFaac.cmake") +execute_process(COMMAND "${CMAKE_COMMAND}" ${FAAC_PREPARE_ARGS} + RESULT_VARIABLE FAAC_PREPARE_RESULT) +if(NOT FAAC_PREPARE_RESULT EQUAL 0) + message(FATAL_ERROR "FAAC source preparation failed; see verification error above") +endif() +# Recheck even incremental builds: a changed input or tampered copy must not +# bypass validation just because CMake did not need to configure again. +add_custom_target(faac_source_verify + COMMAND "${CMAKE_COMMAND}" ${FAAC_PREPARE_ARGS} VERBATIM) # Set up vendored FAAC library -set(FAAC_SOURCES - "${CMAKE_CURRENT_SOURCE_DIR}/third_party/faac/libfaac/bitstream.c" - "${CMAKE_CURRENT_SOURCE_DIR}/third_party/faac/libfaac/blockswitch.c" - "${CMAKE_CURRENT_SOURCE_DIR}/third_party/faac/libfaac/channels.c" - "${CMAKE_CURRENT_SOURCE_DIR}/third_party/faac/libfaac/cpu_compute.c" - "${CMAKE_CURRENT_SOURCE_DIR}/third_party/faac/libfaac/faac.c" - "${CMAKE_CURRENT_SOURCE_DIR}/third_party/faac/libfaac/fft.c" - "${CMAKE_CURRENT_SOURCE_DIR}/third_party/faac/libfaac/filtbank.c" - "${CMAKE_CURRENT_SOURCE_DIR}/third_party/faac/libfaac/frame.c" - "${CMAKE_CURRENT_SOURCE_DIR}/third_party/faac/libfaac/huff2.c" - "${CMAKE_CURRENT_SOURCE_DIR}/third_party/faac/libfaac/huffdata.c" - "${CMAKE_CURRENT_SOURCE_DIR}/third_party/faac/libfaac/quantize.c" - "${CMAKE_CURRENT_SOURCE_DIR}/third_party/faac/libfaac/ratecontrol.c" - "${CMAKE_CURRENT_SOURCE_DIR}/third_party/faac/libfaac/resample.c" - "${CMAKE_CURRENT_SOURCE_DIR}/third_party/faac/libfaac/sbr.c" - "${CMAKE_CURRENT_SOURCE_DIR}/third_party/faac/libfaac/sbr_analysis.c" - "${CMAKE_CURRENT_SOURCE_DIR}/third_party/faac/libfaac/sbr_bitstream.c" - "${CMAKE_CURRENT_SOURCE_DIR}/third_party/faac/libfaac/sbr_tables.c" - "${CMAKE_CURRENT_SOURCE_DIR}/third_party/faac/libfaac/stereo.c" - "${CMAKE_CURRENT_SOURCE_DIR}/third_party/faac/libfaac/tns.c" - "${CMAKE_CURRENT_SOURCE_DIR}/third_party/faac/libfaac/util.c" -) +include("${CMAKE_CURRENT_SOURCE_DIR}/patches/faac/manifest.cmake") +set(FAAC_SOURCES "") +foreach(FAAC_UNIT IN LISTS FAAC_TRANSLATION_UNITS) + list(APPEND FAAC_SOURCES "${FAAC_PREPARED_ROOT}/${FAAC_UNIT}") +endforeach() add_library(faac_lib STATIC ${FAAC_SOURCES}) +add_dependencies(faac_lib faac_source_verify) target_compile_definitions(faac_lib PRIVATE PACKAGE_VERSION="2.2.0" PACKAGE="faac" @@ -502,8 +505,8 @@ target_compile_definitions(faac_lib PRIVATE FAAC_SBR_DECIMATION=1 ) target_include_directories(faac_lib PUBLIC - "${CMAKE_CURRENT_SOURCE_DIR}/third_party/faac/include" - "${CMAKE_CURRENT_SOURCE_DIR}/third_party/faac/libfaac" + "${FAAC_PREPARED_ROOT}/include" + "${FAAC_PREPARED_ROOT}/libfaac" ) if(UNIX) target_link_libraries(faac_lib PUBLIC m) diff --git a/cmake/PrepareFaac.cmake b/cmake/PrepareFaac.cmake new file mode 100644 index 000000000..b707e4e36 --- /dev/null +++ b/cmake/PrepareFaac.cmake @@ -0,0 +1,240 @@ +# Standalone preparation: intentionally uses only CMake 3.10 and Git. +cmake_minimum_required(VERSION 3.10) +function(faac_fail text) + if(DEFINED FAAC_STAGE AND IS_DIRECTORY "${FAAC_STAGE}" AND NOT IS_SYMLINK "${FAAC_STAGE}") + file(REMOVE_RECURSE "${FAAC_STAGE}") + endif() + message(FATAL_ERROR "FAAC preparation: ${text}") +endfunction() +function(no_symlinks path) + get_filename_component(cursor "${path}" ABSOLUTE) + while(NOT cursor STREQUAL "") + if(IS_SYMLINK "${cursor}") + faac_fail("symlink refused: ${cursor}") + endif() + get_filename_component(parent "${cursor}" DIRECTORY) + if(parent STREQUAL cursor) + break() + endif() + set(cursor "${parent}") + endwhile() +endfunction() +function(record_parts record path_var hash_var) + string(REPLACE "|" ";" parts "${record}") + list(LENGTH parts count) + if(NOT count EQUAL 2) + faac_fail("invalid hash record") + endif() + list(GET parts 0 path) + list(GET parts 1 digest) + if(NOT path MATCHES "^((libfaac|include)/[A-Za-z0-9_./-]+|COPYING)$" OR + path MATCHES "(^|/)\.\.(/|$)" OR path MATCHES "//") + faac_fail("unsafe path: ${path}") + endif() + string(LENGTH "${digest}" length) + if(NOT length EQUAL 64 OR NOT digest MATCHES "^[0-9a-f]+$") + faac_fail("invalid hash for ${path}") + endif() + set(${path_var} "${path}" PARENT_SCOPE) + set(${hash_var} "${digest}" PARENT_SCOPE) +endfunction() +function(inventory root expected label) + no_symlinks("${root}") + file(GLOB_RECURSE entries LIST_DIRECTORIES true RELATIVE "${root}" + "${root}/libfaac/*" "${root}/include/*") + set(actual "COPYING") + foreach(path IN LISTS entries) + no_symlinks("${root}/${path}") + if(NOT IS_DIRECTORY "${root}/${path}") + list(APPEND actual "${path}") + endif() + endforeach() + list(SORT actual) + set(sorted_expected "${expected}") + list(SORT sorted_expected) + if(NOT actual STREQUAL sorted_expected) + faac_fail("${label} inventory mismatch") + endif() +endfunction() +function(check_hashes root records label) + foreach(record IN LISTS records) + record_parts("${record}" path digest) + no_symlinks("${root}/${path}") + if(NOT EXISTS "${root}/${path}" OR IS_DIRECTORY "${root}/${path}") + faac_fail("${label} inventory missing ${path}") + endif() + file(SHA256 "${root}/${path}" actual) + if(NOT actual STREQUAL digest) + faac_fail("${label} hash mismatch: ${path}") + endif() + endforeach() +endfunction() +function(json_escape value output) + string(REPLACE "\\" "\\\\" value "${value}") + string(REPLACE "\"" "\\\"" value "${value}") + string(REPLACE "\n" "\\n" value "${value}") + string(REPLACE "\r" "\\r" value "${value}") + string(REPLACE "\t" "\\t" value "${value}") + set(${output} "${value}" PARENT_SCOPE) +endfunction() +foreach(required FAAC_UPSTREAM_DIR FAAC_BUILD_DIR FAAC_PATCH_DIR) + if(NOT DEFINED ${required} OR "${${required}}" STREQUAL "") + faac_fail("missing ${required}") + endif() + get_filename_component(${required} "${${required}}" ABSOLUTE) + no_symlinks("${${required}}") +endforeach() +# Neither source nor destination may contain the other. +string(FIND "${FAAC_BUILD_DIR}/" "${FAAC_UPSTREAM_DIR}/" nested_output) +string(FIND "${FAAC_UPSTREAM_DIR}/" "${FAAC_BUILD_DIR}/" nested_source) +if(nested_output EQUAL 0 OR nested_source EQUAL 0) + faac_fail("source/output overlap") +endif() +no_symlinks("${FAAC_PATCH_DIR}/manifest.cmake") +no_symlinks("${FAAC_PATCH_DIR}/manifest.json") +include("${FAAC_PATCH_DIR}/manifest.cmake") +file(SHA256 "${FAAC_PATCH_DIR}/manifest.json" actual_manifest) +if(NOT actual_manifest STREQUAL FAAC_MANIFEST_SHA256) + faac_fail("manifest hash mismatch") +endif() +set(patch "${FAAC_PATCH_DIR}/0001-bound-input-pointer-arithmetic.patch") +no_symlinks("${patch}") +file(SHA256 "${patch}" actual_patch) +if(NOT actual_patch STREQUAL FAAC_PATCH_SHA256) + faac_fail("patch hash mismatch") +endif() +find_package(Git REQUIRED) +# This process must not inherit another checkout's Git index/worktree. +unset(ENV{GIT_DIR}) +unset(ENV{GIT_WORK_TREE}) +unset(ENV{GIT_INDEX_FILE}) +execute_process(COMMAND "${GIT_EXECUTABLE}" -C "${FAAC_UPSTREAM_DIR}" rev-parse HEAD + RESULT_VARIABLE result OUTPUT_VARIABLE actual_commit OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET) +if(NOT result EQUAL 0 OR NOT actual_commit STREQUAL FAAC_UPSTREAM_COMMIT) + faac_fail("upstream commit mismatch") +endif() +if(DEFINED FAAC_SUPERPROJECT_DIR) + no_symlinks("${FAAC_SUPERPROJECT_DIR}") + file(RELATIVE_PATH gitlink_path "${FAAC_SUPERPROJECT_DIR}" "${FAAC_UPSTREAM_DIR}") + execute_process(COMMAND "${GIT_EXECUTABLE}" -C "${FAAC_SUPERPROJECT_DIR}" + ls-files --stage -- "${gitlink_path}" + RESULT_VARIABLE result OUTPUT_VARIABLE gitlink OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET) + set(expected_gitlink "160000 ${FAAC_UPSTREAM_COMMIT} 0\t${gitlink_path}") + if(NOT result EQUAL 0 OR NOT gitlink STREQUAL expected_gitlink) + faac_fail("superproject gitlink mismatch") + endif() +endif() +set(input_paths "") +foreach(record IN LISTS FAAC_INPUT_RECORDS) + record_parts("${record}" path digest) + if(path IN_LIST input_paths) + faac_fail("duplicate input path") + endif() + list(APPEND input_paths "${path}") + set("input_${path}" "${digest}") +endforeach() +set(output_paths "") +foreach(record IN LISTS FAAC_OUTPUT_RECORDS) + record_parts("${record}" path digest) + list(APPEND output_paths "${path}") + set("output_${path}" "${digest}") +endforeach() +if(NOT input_paths STREQUAL output_paths) + faac_fail("input/output inventory mismatch") +endif() +set(checked_units "") +foreach(unit IN LISTS FAAC_TRANSLATION_UNITS) + if(NOT unit IN_LIST input_paths OR NOT unit MATCHES "\\.c$" OR unit IN_LIST checked_units) + faac_fail("invalid translation unit inventory") + endif() + list(APPEND checked_units "${unit}") +endforeach() +if(NOT checked_units) + faac_fail("empty translation unit inventory") +endif() +inventory("${FAAC_UPSTREAM_DIR}" "${input_paths}" "input") +check_hashes("${FAAC_UPSTREAM_DIR}" "${FAAC_INPUT_RECORDS}" "input") +# Only these private implementation files may be changed by the diff. Git's +# numstat parses actual diff paths; reject additions/deletions/renames via the +# fixed input/output inventory and postimage hashes as well. +execute_process(COMMAND "${GIT_EXECUTABLE}" -C "${FAAC_UPSTREAM_DIR}" apply --numstat "${patch}" + RESULT_VARIABLE result OUTPUT_VARIABLE stats ERROR_VARIABLE error) +if(NOT result EQUAL 0) + faac_fail("patch path parse failed: ${error}") +endif() +string(REPLACE "\n" ";" lines "${stats}") +set(changed "") +foreach(line IN LISTS lines) + if(line STREQUAL "") + continue() + endif() + if(NOT line MATCHES "^[0-9]+\t[0-9]+\t(libfaac/(frame.c|faac.c|faac_internal.h))$") + faac_fail("patch path outside allowed scope: ${line}") + endif() + list(APPEND changed "${CMAKE_MATCH_1}") +endforeach() +if(NOT changed) + faac_fail("patch path list is empty") +endif() +json_escape("${FAAC_UPSTREAM_DIR}" source_json) +json_escape("${FAAC_BUILD_DIR}" build_json) +json_escape("${FAAC_COMPILER}" compiler_json) +json_escape("${FAAC_BUILD_PROFILE}" profile_json) +set(provenance "{\n \"schema_version\": 1,\n \"upstream_commit\": \"${FAAC_UPSTREAM_COMMIT}\",\n \"patch_sha256\": \"${FAAC_PATCH_SHA256}\",\n \"manifest_sha256\": \"${FAAC_MANIFEST_SHA256}\",\n \"source_root\": \"${source_json}\",\n \"build_root\": \"${build_json}\",\n \"compiler\": \"${compiler_json}\",\n \"build_profile\": \"${profile_json}\",\n \"files\": [") +set(separator "") +set(units "") +set(unit_separator "") +foreach(path IN LISTS input_paths) + json_escape("${FAAC_BUILD_DIR}/${path}" compiled_json) + string(APPEND provenance "${separator}\n {\"upstream_path\":\"${path}\",\"input_sha256\":\"${input_${path}}\",\"output_sha256\":\"${output_${path}}\",\"compiled_path\":\"${compiled_json}\"}") + set(separator ",") + if(path IN_LIST FAAC_TRANSLATION_UNITS) + string(APPEND units "${unit_separator}\n \"${compiled_json}\"") + set(unit_separator ",") + endif() +endforeach() +string(APPEND provenance "\n ],\n \"translation_units\": [${units}\n ]\n}\n") +if(EXISTS "${FAAC_BUILD_DIR}") + inventory("${FAAC_BUILD_DIR}" "${output_paths}" "output") + check_hashes("${FAAC_BUILD_DIR}" "${FAAC_OUTPUT_RECORDS}" "output") + no_symlinks("${FAAC_BUILD_DIR}/provenance.json") + if(NOT EXISTS "${FAAC_BUILD_DIR}/provenance.json") + faac_fail("missing output provenance; use a fresh owned build directory") + endif() + file(READ "${FAAC_BUILD_DIR}/provenance.json" existing) + if(NOT existing STREQUAL provenance) + faac_fail("output provenance mismatch; use a fresh owned build directory") + endif() + return() +endif() +get_filename_component(parent "${FAAC_BUILD_DIR}" DIRECTORY) +file(MAKE_DIRECTORY "${parent}") +string(RANDOM LENGTH 16 ALPHABET 0123456789abcdef token) +set(FAAC_STAGE "${parent}/.faac-stage-${token}") +if(EXISTS "${FAAC_STAGE}" OR IS_SYMLINK "${FAAC_STAGE}") + faac_fail("staging collision") +endif() +file(MAKE_DIRECTORY "${FAAC_STAGE}") +foreach(path IN LISTS input_paths) + get_filename_component(dir "${FAAC_STAGE}/${path}" DIRECTORY) + file(MAKE_DIRECTORY "${dir}") + configure_file("${FAAC_UPSTREAM_DIR}/${path}" "${FAAC_STAGE}/${path}" COPYONLY) +endforeach() +# Stop Git discovery at the stage parent: apply to files, never the workspace +# index or a submodule. Git metadata is neither created nor copied here. +set(ENV{GIT_CEILING_DIRECTORIES} "${parent}") +execute_process(COMMAND "${GIT_EXECUTABLE}" -C "${FAAC_STAGE}" apply --check "${patch}" + RESULT_VARIABLE result ERROR_VARIABLE error) +if(NOT result EQUAL 0) + faac_fail("patch check failed: ${error}") +endif() +execute_process(COMMAND "${GIT_EXECUTABLE}" -C "${FAAC_STAGE}" apply "${patch}" + RESULT_VARIABLE result ERROR_VARIABLE error) +if(NOT result EQUAL 0) + faac_fail("patch application failed: ${error}") +endif() +inventory("${FAAC_STAGE}" "${output_paths}" "output") +check_hashes("${FAAC_STAGE}" "${FAAC_OUTPUT_RECORDS}" "output") +file(WRITE "${FAAC_STAGE}/provenance.json" "${provenance}") +file(RENAME "${FAAC_STAGE}" "${FAAC_BUILD_DIR}") +unset(FAAC_STAGE) diff --git a/db/migrations/0090_add_stale_deletion_guards.sql b/db/migrations/0090_add_stale_deletion_guards.sql new file mode 100644 index 000000000..097e90bbd --- /dev/null +++ b/db/migrations/0090_add_stale_deletion_guards.sql @@ -0,0 +1,30 @@ +-- Durable identity guards for narrow stale-incomplete recording cleanup. +-- Ordinary deletion objects retain NULL guards and their existing semantics. + +-- migrate:up + +ALTER TABLE storage_deletion_objects ADD COLUMN stale_guard_kind TEXT + CHECK (stale_guard_kind IS NULL OR stale_guard_kind IN ('missing', 'empty')); +ALTER TABLE storage_deletion_objects ADD COLUMN stale_guard_dev INTEGER; +ALTER TABLE storage_deletion_objects ADD COLUMN stale_guard_ino INTEGER + CHECK (CASE + WHEN stale_guard_kind IS NULL THEN stale_guard_dev IS NULL AND stale_guard_ino IS NULL + WHEN stale_guard_kind = 'missing' THEN stale_guard_dev IS NULL AND stale_guard_ino IS NULL + WHEN stale_guard_kind = 'empty' THEN stale_guard_dev IS NOT NULL AND stale_guard_dev >= 0 + AND stale_guard_ino IS NOT NULL AND stale_guard_ino > 0 + ELSE 0 + END); + +-- migrate:down + +-- Removing these guards can make a pending operation delete replacement footage. +-- Downgrade is unsupported even when no guarded work currently appears pending; +-- do not run an older binary against this database. Restore a separately verified +-- pre-upgrade backup under a separately qualified recovery procedure instead. +CREATE TEMP TABLE IF NOT EXISTS stale_guard_downgrade_blocker (value INTEGER); +CREATE TEMP TRIGGER IF NOT EXISTS prevent_stale_guard_downgrade +BEFORE INSERT ON stale_guard_downgrade_blocker +BEGIN + SELECT RAISE(ABORT, 'stale deletion guards cannot be downgraded'); +END; +INSERT INTO stale_guard_downgrade_blocker VALUES (1); diff --git a/docs/TESTING.md b/docs/TESTING.md new file mode 100644 index 000000000..e49769786 --- /dev/null +++ b/docs/TESTING.md @@ -0,0 +1,141 @@ +# Running tests + +Use Node 24 from [.nvmrc](../.nvmrc), install the root and web lockfiles, and +follow [BUILD.md](BUILD.md) for native dependencies. Build the web assets before +server-backed tests. Playwright UI runs also require Chromium: + +```sh +npm ci +npm ci --prefix web +npm --prefix web run build +npx playwright install chromium +``` + +## Discover tests without starting a server + +```sh +npm test -- --list +npm run test:api -- --list +npm run test:ui -- --list +npm run test:go2rtc -- --list +``` + +Discovery does not build or inspect a native binary, acquire a runtime lease, +create a temporary server directory, or run global setup. It writes a unique +discovery report under `build/eng-artifacts//`. Empty selections and +invalid filters fail. + +`npm test` selects all existing projects. API/UI scripts retain their `@api` and +`@ui` tag filters. The go2rtc script selects `@go2rtc` tests in the API project, +where the current `go2rtc.api.spec.ts` actually lives. The legacy project named +`go2rtc` currently matches no files; selecting it directly correctly fails. + +## Run integration tests + +```sh +npm run test:api +npm run test:ui -- --shard 1/2 +npm run test:go2rtc +``` + +The scripts invoke the owned integration runner. Without `--binary`, it builds +a Release server with go2rtc enabled and SOD/LiteRT/MQTT disabled in a task-scoped +build directory. Initialize the `go2rtc` and FAAC sources and build +`go2rtc/go2rtc` before running media fixtures. This profile does not qualify +optional features. Supply an explicit appropriately built binary when testing +another feature profile: + +```sh +node scripts/quality/run-integration.mjs \ + --task local-api --project api --grep @api \ + --binary build/bin/lightnvr +``` + +Use repeatable `--file` options for exact spec files. Paths must resolve inside +`tests/integration`; arbitrary Playwright configs, positional patterns, worker +counts, production URLs and environment-provided server configs are rejected. +Both `--shard 1/2` and `--shard=1/2` work. `--list` can be added to any selection +without needing the referenced binary to exist. + +```sh +node scripts/quality/run-integration.mjs \ + --task local-streams --project ui \ + --file tests/integration/specs/streams.ui.spec.ts --list +``` + +The fixed test ports are 18080 (LightNVR) and 11984/18554/18555 (go2rtc). +Existing listeners cause failure; no process is adopted or killed by name. +Native and integration runs share an exclusive runtime lease, so run these +commands serially. A forced termination can leave a stale lease; inspect the +retained run/process records and confirm that no run is active before recovering +it. The runner never silently steals a lock. + +## Health and MQTT lane + +Use the bounded `health` configuration and a binary built with `ENABLE_MQTT=ON` +and `ENABLE_GO2RTC=OFF`. The profile keeps MQTT enabled, disables go2rtc, and +retains the existing health collector settings. An explicit binary is required +for execution so the default MQTT-disabled build cannot substitute for it. + +```sh +node scripts/quality/run-integration.mjs \ + --task local-health --project all --config-profile health \ + --binary build/health-ci/bin/lightnvr \ + --file tests/integration/specs/system-health.api.spec.ts \ + --file tests/integration/specs/system-health-mqtt.api.spec.ts \ + --file tests/integration/specs/system-health.ui.spec.ts \ + --file tests/integration/specs/system-health-settings.ui.spec.ts +``` + +This API/UI lane intentionally uses an unreachable local broker at port 18884 +for delivery-observability tests. The runner refuses to use an existing broker +on that port. The separate real-broker presence script remains a separate lane; +its broker-visible assertions have not been replaced by this configuration. + +## Reports and cleanup + +The runner prints the artifact directory. It contains discovery/results JSON, +HTML report, Playwright output, per-test screenshots/traces, process cleanup +results, and an allowlisted copy of the owned server configuration/logs under +`runtime/`. Database and recording directories are retained in the unique +`/tmp/lightnvr-eng-*` run directory for local diagnosis, but are not broadly +uploaded. No unrelated `/tmp` directories are collected or removed. + +Open a specific report using its printed path: + +```sh +npm run test:report -- build/eng-artifacts/local-api/integration-RUN_ID/html +``` + +A few older explicit screenshot calls still write to repository `test-results/`; +the serialized runner and CI's separate job checkouts prevent collisions, and +CI retains those screenshots separately. Normal Playwright artifacts use the +unique per-run directory. + +Cleanup validates the owned executable, command line and kernel start time and +uses Linux pidfds. The platform requires Linux `/proc` and Python 3 with pidfd +support. A 60-second graceful shutdown timeout or leftover child processes +fails the run even when test bodies pass. There is no broad `pkill`, raw PID-file +fallback, or shared temporary-directory deletion. + +## Native and static checks + +```sh +python3 scripts/quality/run-native.py \ + --task local-native --regex '^test_system_health_proc$' --sanitizer asan-ubsan +npm run typecheck:tests +npm --prefix web run typecheck +npm --prefix web test -- --runInBand +python3 -m unittest discover -s tests/quality -p test_runners.py +``` + +The native runner defaults to the lean ASan/UBSan profile. `--profile integration` +enables go2rtc; `--profile full` enables SOD/LiteRT/go2rtc/MQTT. `--sanitizer none` +selects Release. It checks system FFmpeg header/runtime ABI agreement before +building and fails if no CTest test matches. Test registration fragments belong +under `tests/unit/cmake/eng/`. + +Passing runner tests or discovery does not establish full application acceptance. +The [T13 evidence](../quality/evidence/T13.md) records real API and lifecycle +failures separately from harness success. Physical qualification and pending +security exception reviews remain separate acceptance gates. diff --git a/docs/api/contracts/common.yaml b/docs/api/contracts/common.yaml new file mode 100644 index 000000000..95c42368b --- /dev/null +++ b/docs/api/contracts/common.yaml @@ -0,0 +1,272 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# Copyright (C) 2025-2026 OpenSensor Engineering and LightNVR contributors +# +# Shared contract fragment for the LightNVR OpenAPI 3.1 definition. +# Root document: docs/api/openapi.yaml. Owner of this fragment: T11 (pilot); +# later schema workers edit their assigned fragments only (see plan T12/T25). +# +# This is NOT a standalone OpenAPI document. It is a $ref target: every +# reusable object lives under `components` so JSON pointers look like +# `./common.yaml#/components/schemas/ErrorResponse`. +# +# Every schema below was checked against the C sources named in its +# `x-lightnvr-source` annotation. Byte limits come from the C buffer sizes; +# JSON Schema `maxLength` counts code points, so `maxLength` equals the byte +# limit and is exact only for ASCII (see `x-lightnvr-max-bytes`). +components: + schemas: + ErrorResponse: + description: >- + Error envelope used for every non-2xx JSON response in the pilot. + Produced only by http_response_set_json_error(), which emits exactly one + key. Clients must not depend on the message text for control flow except + where an operation documents a specific message. + type: object + required: [error] + properties: + error: + type: string + minLength: 1 + x-lightnvr-source: src/web/request_response.c:http_response_set_json_error + + Int32: + description: >- + C `int` read through cJSON `valueint` (fractional values are truncated, + out-of-range values saturate). Used where the server performs no range + validation of its own. + type: integer + minimum: -2147483648 + maximum: 2147483647 + + EpochSecondsOrZero: + description: Unix epoch seconds (int64). `0` means "never observed". + type: integer + minimum: 0 + x-lightnvr-source: include/core/camera_selector.h:fleet_camera_t (int64_t); include/telemetry/stream_metrics.h last_frame_ts + + CameraUuid: + description: >- + Immutable fleet identity (36-char UUID). Legacy rows created before + camera UUIDs existed may carry an empty string. + type: string + maxLength: 36 + pattern: '^(|[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})$' + x-lightnvr-source: include/core/config.h:CAMERA_UUID_STRING_SIZE (37 incl. NUL) + + StreamName: + description: >- + Stream (camera) name as stored. This is the primary key used in paths. + Names created through POST /api/streams must match + `^[A-Za-z0-9_-][A-Za-z0-9._-]*$` (is_valid_stream_name), but names that + predate that rule or arrive through configuration import can contain + other characters, so the read-side schema only bounds the length. + type: string + minLength: 1 + maxLength: 255 + x-lightnvr-max-bytes: 255 + x-lightnvr-create-pattern: '^[A-Za-z0-9_-][A-Za-z0-9._-]*$' + x-lightnvr-source: include/core/config.h:MAX_STREAM_NAME; src/core/path_utils.c:is_valid_stream_name + + PlaybackTransport: + type: string + enum: [auto, webrtc_only, mse_only, hls_only, webrtc_then_mse, mse_then_hls] + description: >- + Responses substitute `auto` when the stored value is invalid, so a + response never carries a value outside this enum. + x-lightnvr-source: src/core/config.c:playback_transport_is_valid + + StreamProtocol: + description: >- + Transport protocol: 0 = TCP, 1 = UDP (stream_protocol_t). PUT does not + range-check this value (see finding candidate F-T11-06); the contract + only permits the two defined values. + type: integer + enum: [0, 1] + x-lightnvr-source: include/core/config.h:stream_protocol_t + + RetentionDays: + description: >- + Tri-state per-stream retention override in days. + `-1` inherits the live global retention value at evaluation time, + `0` is explicitly unlimited (also disables tier expiry), and `1..365` + is a day override. Writes outside -1..365 are rejected with 400. + For `detection_retention_days`, `-1` inherits the *global* retention + value, not this stream's `retention_days`. + type: integer + minimum: -1 + maximum: 365 + x-lightnvr-retention-states: + inherit: -1 + unlimited: 0 + finite: '1..365' + x-lightnvr-source: >- + include/core/config.h:stream_config_t.retention_days; + src/web/api_handlers_streams_modify.c:handle_put_stream (range check); + src/storage/storage_manager.c (tri-state evaluation, tier expiry skip on 0) + + HourOfWeekSchedule: + description: >- + 168 booleans indexed `day * 24 + hour` in server local time, where day + 0 = Sunday ... 6 = Saturday (struct tm tm_wday). Responses always carry + exactly 168 entries. On PUT, an array whose length is not 168 is + silently ignored (no error) and any entry other than JSON `true` is + stored as false (finding candidate F-T11-07). + type: array + minItems: 168 + maxItems: 168 + items: + type: boolean + x-lightnvr-source: >- + src/web/api_handlers_streams_get.c (serialization loop j<168); + src/web/api_handlers_streams_modify.c:handle_put_stream (arr_size == 168); + src/video/stream_manager.c:is_recording_scheduled (tm_wday*24+tm_hour) + + EmptyString: + description: Redacted value. The server emits an empty string in place of a sensitive value. + type: string + const: '' + + UrlWithoutUserinfo: + description: >- + A URL-like string that carries no userinfo (`user:pass@`) in its + authority. Empty string is permitted (viewer redaction falls back to "" + when credential stripping fails). A response that contains userinfo here + is a credential leak and must fail validation. + type: string + maxLength: 511 + pattern: '^(?![A-Za-z][A-Za-z0-9+.-]*://[^/?#]*@)' + x-lightnvr-source: src/web/api_handlers_streams_get.c:get_stream_api_credentials; src/core/url_utils.c:url_strip_credentials + + UrlWithoutQuerySecrets: + description: >- + Rejects a URL whose query string carries a non-empty parameter whose key + contains pass, pwd, token, secret or apikey (case-insensitive). The C + redaction only strips userinfo, so a camera URL like + `rtsp://host/live?user=admin&password=x` currently reaches viewers + unchanged. This rule is the redaction policy the contract enforces; a + server response that violates it is a finding (F-T11-03), not a schema + bug. + type: string + not: + pattern: '[?&;][^=&#]*([Pp][Aa][Ss][Ss]|[Pp][Ww][Dd]|[Tt][Oo][Kk][Ee][Nn]|[Ss][Ee][Cc][Rr][Ee][Tt]|[Aa][Pp][Ii]_?[Kk][Ee][Yy])[^=&#]*=[^&#]' + + ViewerSafeUrl: + description: Credential-free URL as delivered to principals without camera.configure. + allOf: + - $ref: '#/components/schemas/UrlWithoutUserinfo' + - $ref: '#/components/schemas/UrlWithoutQuerySecrets' + + parameters: + StreamNamePath: + name: name + in: path + required: true + description: >- + Stream name, percent-encoded as one path segment (the UI uses + encodeURIComponent). Routing matches the *encoded* segment against + `#` (a run of non-'/' characters), then http_request_extract_path_param + decodes it with form semantics: `%XX` is decoded and a literal `+` + becomes a space, so a name containing '+' must be sent as `%2B`. + The entire encoded suffix after `/api/streams/` must be shorter than + 256 bytes, including `/full` or `/zones`. Thus the encoded name limit + is 255 bytes for PUT, 250 for `/full`, and 249 for `/zones`. Overflow + answers 400 "Invalid request path" ("Invalid stream name" for zones; + finding candidate F-T11-09). + Names whose decoded form contains '/' (sent as `%2F`) are truncated at + the first '/' by the `/full` handler and resolve to a different stream + (finding candidate F-T11-08). Path segments that decode to `.` or `..` + are rejected by the HTTP parser before routing. + schema: + $ref: '#/components/schemas/StreamName' + x-lightnvr-encoding: percent-encode-path-segment + x-lightnvr-source: >- + src/web/request_response.c:http_request_extract_path_param,url_decode; + src/web/libuv_connection.c:on_url_complete,request_path_has_dot_segment + + headers: + CacheControl: + description: 'Every JSON response sets `no-cache, no-store, must-revalidate` (plus `Pragma: no-cache` and `Expires: 0`).' + schema: + type: string + const: no-cache, no-store, must-revalidate + x-lightnvr-source: src/web/request_response.c:http_response_set_json + + responses: + BadRequest: + description: Malformed path, query, or body. Message text is operation-specific. + headers: + Cache-Control: + $ref: '#/components/headers/CacheControl' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + Unauthorized: + description: >- + No authenticated principal (and demo mode is off). Body is + `{"error":"Unauthorized"}`. + headers: + Cache-Control: + $ref: '#/components/headers/CacheControl' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + example: + error: Unauthorized + x-lightnvr-source: src/web/httpd_utils.c:httpd_authorize_stream_action_with_context,httpd_authorize_action + Forbidden: + description: Authenticated but the authorization policy denied the action. Body is `{"error":"Forbidden"}`. + headers: + Cache-Control: + $ref: '#/components/headers/CacheControl' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + example: + error: Forbidden + x-lightnvr-source: src/web/httpd_utils.c:httpd_authorize_stream_action_with_context,httpd_authorize_action + StreamNotFound: + description: >- + `{"error":"Stream not found"}` when no stream with that name is loaded. + This check runs BEFORE authentication, so unauthenticated callers can + distinguish existing from missing names (finding candidate F-T11-01). + `{"error":"Camera not found"}` is returned after authentication when the + stream exists but has no fleet inventory row. + headers: + Cache-Control: + $ref: '#/components/headers/CacheControl' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + examples: + stream: + value: + error: Stream not found + camera: + value: + error: Camera not found + x-lightnvr-source: src/web/api_handlers_streams_get.c:handle_get_stream_full; src/web/httpd_utils.c:httpd_authorize_stream_action_with_context + PayloadTooLarge: + description: A size-checked string field exceeded its buffer. + headers: + Cache-Control: + $ref: '#/components/headers/CacheControl' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + InternalError: + description: >- + Server-side failure (allocation, serialization, database read, or + authorization policy evaluation error: `{"error":"Authorization policy + evaluation failed"}`). + headers: + Cache-Control: + $ref: '#/components/headers/CacheControl' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' diff --git a/docs/api/contracts/streams.yaml b/docs/api/contracts/streams.yaml new file mode 100644 index 000000000..2f7f1a7f3 --- /dev/null +++ b/docs/api/contracts/streams.yaml @@ -0,0 +1,1209 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# Copyright (C) 2025-2026 OpenSensor Engineering and LightNVR contributors +# +# Streams pilot contract fragment (T11). Root: docs/api/openapi.yaml. +# Not a standalone OpenAPI document: `paths` entries are Path Item $ref +# targets and `components` holds the schemas they use. +# +# Pilot scope: Streams list + edit (GET /api/streams?summary=true, +# GET /api/streams/{name}/full, PUT /api/streams/{name}) and the read-only +# option dependencies the list/edit UI loads (client-config, detection models, +# auth verify, zones read). Every other Streams operation is inventoried in +# quality/api-inventory.json (streams_pilot.out_of_pilot_streams_operations) +# and intentionally NOT specified here. +# +# Modelling rules used throughout: +# - The server never emits JSON null in these responses; absent text is "". +# - Response objects are open (additive fields are compatible) EXCEPT that +# viewer-facing schemas explicitly forbid or pin every credential-bearing +# key, and LPR summary rows are closed (identity only). +# - Request objects are closed: the server silently ignores unknown keys, so +# the contract forbids them to catch client drift (e.g. D-01 `is_deleted`). +# - Numeric bounds appear only where the C handler enforces them. Intended but +# unenforced ranges are recorded as `x-lightnvr-intended-range`. +# - `x-lightnvr-overflow` says what the server does with an over-long string: +# `truncate` (silently, 202) or the status it returns. +paths: + /api/streams: + get: + operationId: listStreams + tags: [streams] + summary: List streams (paged summary for the pilot; legacy full list otherwise) + description: >- + Authentication is checked first (401). With `summary=true` (or `1`) the + paged summary variant is returned; its row shape depends on `surface`. + Rows are filtered per camera: `admin`/`live` require live.view, + `lpr` requires both lpr.search and lpr.read. `can_configure` is the + per-camera camera.configure decision. Query parameters are validated in + this order: page/page_size, sort_by, sort_order, surface, availability. + Without `summary`, the legacy unbounded array of full stream objects is + returned (out of pilot; no current web/js caller) and every other query + parameter is ignored. + x-lightnvr-source: src/web/api_handlers_streams_get.c:handle_get_streams,handle_get_stream_summaries + x-lightnvr-authz: + authentication: required (demo mode grants a viewer pseudo-user; auth disabled grants admin) + row_filter: + admin: live.view + live: live.view + lpr: lpr.search AND lpr.read + can_configure: camera.configure (per camera) + x-lightnvr-response-variant-by-query: + parameter: surface + requires: { summary: ['true', '1'] } + mapping: + admin: '#/components/schemas/StreamSummaryPageAdmin' + live: '#/components/schemas/StreamSummaryPageLive' + lpr: '#/components/schemas/StreamSummaryPageLpr' + otherwise: '#/components/schemas/LegacyStreamList' + parameters: + - name: summary + in: query + description: Exactly `true` or `1` selects the summary variant; any other value (including `TRUE`) selects the legacy list. + schema: { type: string } + - name: surface + in: query + schema: { type: string, enum: [admin, live, lpr], default: admin } + description: 'Invalid value: 400 "surface must be admin, live or lpr".' + - name: page + in: query + schema: { type: integer, minimum: 1, maximum: 1000000, default: 1 } + description: >- + Decimal integer parsed by strtol: leading whitespace and a plus sign + are accepted, but trailing whitespace/suffixes are rejected. Empty + or out of range: + 400 "page and page_size must be positive; page_size max is 100". + A page beyond the last returns an empty `streams` array with the real totals. + - name: page_size + in: query + schema: { type: integer, minimum: 1, maximum: 100, default: 50 } + description: 'Out of range: 400 "page and page_size must be positive; page_size max is 100".' + - name: sort_by + in: query + schema: { type: string, enum: [name, status, resolution, fps, recording], default: name } + description: 'Invalid: 400 "Invalid stream summary sort field". Ties break on case-insensitive name.' + - name: sort_order + in: query + schema: { type: string, enum: [asc, desc], default: asc } + description: 'Invalid: 400 "sort_order must be asc or desc".' + - name: availability + in: query + schema: { type: string, enum: [all, live, offline, never_connected, disabled], default: all } + description: 'Invalid: 400 "Invalid availability value".' + - name: search + in: query + description: >- + Case-insensitive substring over name, camera_uuid, tags, location + path and inventory address. Values longer than 255 bytes are + silently truncated. + schema: { type: string, maxLength: 255 } + - name: include_admin_url + in: query + description: >- + Exactly `true` or `1` adds `admin_url` to rows on the admin surface + (never on live/lpr). Rows without camera.configure get "". + schema: { type: string } + responses: + '200': + description: Summary page (surface-specific) or legacy array. + headers: + Cache-Control: + $ref: './common.yaml#/components/headers/CacheControl' + content: + application/json: + schema: + anyOf: + - $ref: '#/components/schemas/StreamSummaryPageAdmin' + - $ref: '#/components/schemas/StreamSummaryPageLive' + - $ref: '#/components/schemas/StreamSummaryPageLpr' + - $ref: '#/components/schemas/LegacyStreamList' + '400': + $ref: './common.yaml#/components/responses/BadRequest' + '401': + $ref: './common.yaml#/components/responses/Unauthorized' + '500': + description: >- + Includes "Authorization policy evaluation failed", which is also + returned for the WHOLE list when any stream lacks a fleet inventory + row (finding candidate F-T11-10). + content: + application/json: + schema: + $ref: './common.yaml#/components/schemas/ErrorResponse' + + /api/streams/{name}/full: + get: + operationId: getStreamFull + tags: [streams] + summary: Full stream detail used by the edit form + description: >- + Order of checks: path decode (400 "Invalid request path"), stream + lookup (404 "Stream not found", BEFORE authentication), configuration + read (500), authentication (401), fleet row (404 "Camera not found"), + live.view (403). Callers with live.view but without camera.configure + receive the live-only redacted variant; `can_control_privacy` equals + the camera.configure decision and selects the variant. The response is + wrapped as `{"stream": {...}}` (GET /api/streams/{name}, out of pilot, + returns the same object unwrapped). The handler never emits + `detection_zones` (D-02); zones come from GET /api/streams/{name}/zones. + x-lightnvr-source: src/web/api_handlers_streams_get.c:handle_get_stream_full,get_stream_api_credentials,stream_user_can_modify + x-lightnvr-authz: + required: live.view (per camera) + variant_selector: camera.configure (per camera) -> can_control_privacy + x-lightnvr-response-variant-by-field: + field: stream.can_control_privacy + mapping: + 'true': '#/components/schemas/StreamFullResponseConfigurable' + 'false': '#/components/schemas/StreamFullResponseLiveOnly' + parameters: + - $ref: './common.yaml#/components/parameters/StreamNamePath' + responses: + '200': + description: Stream detail, configure or live-only variant. + headers: + Cache-Control: + $ref: './common.yaml#/components/headers/CacheControl' + content: + application/json: + schema: + $ref: '#/components/schemas/StreamFullResponse' + '400': + $ref: './common.yaml#/components/responses/BadRequest' + '401': + $ref: './common.yaml#/components/responses/Unauthorized' + '403': + $ref: './common.yaml#/components/responses/Forbidden' + '404': + $ref: './common.yaml#/components/responses/StreamNotFound' + '500': + $ref: './common.yaml#/components/responses/InternalError' + + /api/streams/{name}: + put: + operationId: updateStream + tags: [streams] + summary: Update stream configuration (asynchronous apply) + description: >- + Order of checks: path decode (400 "Invalid request path"), stream + lookup (404 "Stream not found", BEFORE authentication), configuration + read (500), camera.configure authorization (401/404 "Camera not + found"/403) BEFORE the body is parsed, then body parse (400 "Invalid + JSON in request body" for an empty or unparsable body). Fields are + applied in handler order; the first failing validation returns its + error and nothing is stored. Present keys with the wrong JSON type are + silently ignored (F-T11-05). A valid JSON body that is not an object + (e.g. `null`, `[]`) is accepted as a no-op 202 (F-T11-04). + If `isOnvif` is omitted the server recomputes it from whether `url` + contains "onvif" (F-T11-11). When the effective isOnvif is true the + handler copies onvif_username/onvif_password/onvif_profile, moves URL + credentials into them, and runs an ONVIF connection test inline before + responding; when false those three keys are ignored. + `set_privacy_mode` short-circuits: after all other keys were parsed and + validated (and `enable_disabled` already executed) it writes only + privacy_mode and answers 200; other edits in the same body are + discarded (F-T11-12). Otherwise the response is 202 and a detached + worker applies the change; the 202 does not prove persistence. + Errors in the 400 set: "playback_transport is invalid", + "eptz_config must be a JSON string", "eptz_config exceeds size limit", + "eptz_config is not a supported version-1 equidistant ceiling configuration", + "retention_days must be between -1 and 365", + "detection_retention_days must be between -1 and 365", + "detection_url must use http, https, rtsp, or rtsps", + "publish_url destinations must be rtmp:// or rtmps:// URLs". + 413 set: "detection_url exceeds size limit", "publish_url exceeds size limit". + x-lightnvr-source: src/web/api_handlers_streams_modify.c:handle_put_stream,check_stream_write_access,copy_valid_eptz_config,is_allowed_detection_url,normalize_stream_url_credentials + x-lightnvr-authz: + required: camera.configure (per camera), checked before body parse + parameters: + - $ref: './common.yaml#/components/parameters/StreamNamePath' + requestBody: + required: true + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/StreamUpdateRequest' + - $ref: '#/components/schemas/StreamPrivacyModeRequest' + responses: + '200': + description: set_privacy_mode short-circuit result. + headers: + Cache-Control: + $ref: './common.yaml#/components/headers/CacheControl' + content: + application/json: + schema: + $ref: '#/components/schemas/StreamPrivacyModeResponse' + '202': + description: Update accepted; applied asynchronously by a worker thread. + headers: + Cache-Control: + $ref: './common.yaml#/components/headers/CacheControl' + content: + application/json: + schema: + $ref: '#/components/schemas/StreamUpdateAccepted' + '400': + $ref: './common.yaml#/components/responses/BadRequest' + '401': + $ref: './common.yaml#/components/responses/Unauthorized' + '403': + $ref: './common.yaml#/components/responses/Forbidden' + '404': + $ref: './common.yaml#/components/responses/StreamNotFound' + '413': + $ref: './common.yaml#/components/responses/PayloadTooLarge' + '500': + $ref: './common.yaml#/components/responses/InternalError' + + /api/streams/{name}/zones: + get: + operationId: getStreamZones + tags: [streams, pilot-option-dependency] + summary: Detection zones for a stream (read by the edit modal and ZoneEditor) + description: >- + No pre-auth existence check: 401 first, then 404 "Camera not found" + for unknown names, 403 without live.view. Path decode failure is 400 + "Invalid stream name". POST/DELETE on this path are out of pilot. + x-lightnvr-source: src/web/api_handlers_zones.c:handle_get_zones + x-lightnvr-authz: + required: live.view (per camera) + parameters: + - $ref: './common.yaml#/components/parameters/StreamNamePath' + responses: + '200': + description: Zones for the stream (possibly empty). + headers: + Cache-Control: + $ref: './common.yaml#/components/headers/CacheControl' + content: + application/json: + schema: + $ref: '#/components/schemas/ZonesResponse' + '400': + $ref: './common.yaml#/components/responses/BadRequest' + '401': + $ref: './common.yaml#/components/responses/Unauthorized' + '403': + $ref: './common.yaml#/components/responses/Forbidden' + '404': + $ref: './common.yaml#/components/responses/StreamNotFound' + '500': + $ref: './common.yaml#/components/responses/InternalError' + + /api/client-config: + get: + operationId: getClientConfig + tags: [pilot-option-dependency] + summary: Viewer-safe runtime playback configuration + x-lightnvr-source: src/web/api_handlers_settings.c:handle_get_client_config + x-lightnvr-authz: + required: authentication only + responses: + '200': + description: Client configuration. + headers: + Cache-Control: + $ref: './common.yaml#/components/headers/CacheControl' + content: + application/json: + schema: + $ref: '#/components/schemas/ClientConfig' + '401': + $ref: './common.yaml#/components/responses/Unauthorized' + '500': + $ref: './common.yaml#/components/responses/InternalError' + + /api/detection/models: + get: + operationId: listDetectionModels + tags: [pilot-option-dependency] + summary: Detection model options for the edit form + description: >- + 401 unauthenticated; 403 unless the principal has global + camera.configure or camera.configure on at least one camera. Viewers + therefore receive 403 and the UI shows no models (D-04). + x-lightnvr-source: src/web/api_handlers_detection_models.c:handle_get_detection_models + x-lightnvr-authz: + required: camera.configure (global, or on >= 1 camera) + responses: + '200': + description: Built-in models first (motion, api-detection, onvif), then file models. + headers: + Cache-Control: + $ref: './common.yaml#/components/headers/CacheControl' + content: + application/json: + schema: + $ref: '#/components/schemas/DetectionModelsResponse' + '401': + $ref: './common.yaml#/components/responses/Unauthorized' + '403': + $ref: './common.yaml#/components/responses/Forbidden' + '500': + $ref: './common.yaml#/components/responses/InternalError' + + /api/auth/verify: + get: + operationId: verifyAuth + tags: [pilot-option-dependency] + summary: Current principal (role and demo-mode gating for the Streams UI) + description: >- + Three 200 shapes: authentication disabled (synthetic admin), a real + authenticated user, or demo-mode anonymous viewer. Uses + httpd_get_authenticated_user (scoped API tokens are not accepted here + although the streams handlers accept them). 401 also clears the + session cookie. An authenticated user in demo mode receives no + `demo_mode` key (F-T11-13). + x-lightnvr-source: src/web/api_handlers_auth_backend_agnostic.c:handle_auth_verify + security: + - {} + - sessionCookie: [] + - basicAuth: [] + - apiKey: [] + - bearerAuth: [] + responses: + '200': + description: Principal description. + headers: + Cache-Control: + $ref: './common.yaml#/components/headers/CacheControl' + content: + application/json: + schema: + $ref: '#/components/schemas/AuthVerifyResponse' + '401': + $ref: './common.yaml#/components/responses/Unauthorized' + +components: + schemas: + # ------------------------------------------------------------------ + # Summary list (GET /api/streams?summary=true) + # ------------------------------------------------------------------ + SummaryStatus: + type: string + enum: [Running, Degraded, Error, Stopped, Unknown] + x-lightnvr-source: src/web/api_handlers_streams_get.c:summary_status + + Availability: + type: string + enum: [live, offline, never_connected, disabled] + x-lightnvr-source: src/core/camera_selector.c:fleet_availability_state_name + + SummaryForbiddenSensitiveKeys: + description: >- + Summary rows never carry credential-bearing configuration. Presence of + any of these keys is a redaction failure regardless of value. + not: + anyOf: + - required: [url] + - required: [onvif_username] + - required: [onvif_password] + - required: [sub_stream_url] + - required: [detection_url] + - required: [publish_url] + - required: [go2rtc_source_override] + - required: [detection_api_url] + + StreamSummaryLpr: + description: >- + surface=lpr row. LPR discovery grants identity only, so this object is + closed: any extra key (configuration, location, health) is a policy leak. + type: object + additionalProperties: false + required: [camera_uuid, name] + properties: + camera_uuid: + $ref: './common.yaml#/components/schemas/CameraUuid' + name: + $ref: './common.yaml#/components/schemas/StreamName' + + StreamSummaryBase: + description: Fields shared by admin and live summary rows (always emitted). + type: object + allOf: + - $ref: '#/components/schemas/SummaryForbiddenSensitiveKeys' + required: + - camera_uuid + - name + - location_uuid + - location_name + - location_path + - enabled + - width + - height + - fps + - codec + - protocol + - record + - record_on_schedule + - detection_based_recording + - isOnvif + - tags + - status + - availability + - first_video_at + - last_frame_ts + - current_fps + - recording_active + - last_recording_at + - can_configure + properties: + camera_uuid: { $ref: './common.yaml#/components/schemas/CameraUuid' } + name: { $ref: './common.yaml#/components/schemas/StreamName' } + location_uuid: { $ref: './common.yaml#/components/schemas/CameraUuid' } + location_name: { type: string, maxLength: 127 } + location_path: { type: string, maxLength: 1023 } + enabled: { type: boolean } + width: { $ref: './common.yaml#/components/schemas/Int32' } + height: { $ref: './common.yaml#/components/schemas/Int32' } + fps: { $ref: './common.yaml#/components/schemas/Int32' } + codec: { type: string, maxLength: 15 } + protocol: { $ref: './common.yaml#/components/schemas/StreamProtocol' } + record: { type: boolean } + record_on_schedule: { type: boolean } + detection_based_recording: { type: boolean } + isOnvif: { type: boolean } + tags: { type: string, maxLength: 255, description: Comma-separated legacy tags. } + status: { $ref: '#/components/schemas/SummaryStatus' } + availability: { $ref: '#/components/schemas/Availability' } + first_video_at: { $ref: './common.yaml#/components/schemas/EpochSecondsOrZero' } + last_frame_ts: { $ref: './common.yaml#/components/schemas/EpochSecondsOrZero' } + current_fps: { type: number, minimum: 0 } + recording_active: { type: boolean } + last_recording_at: { $ref: './common.yaml#/components/schemas/EpochSecondsOrZero' } + can_configure: + type: boolean + description: Per-camera camera.configure decision. Authoritative for edit affordances (D-05). + x-lightnvr-not-emitted: + - error_message (D-03) + - playback_transport on the admin surface (D-03) + + StreamSummaryAdmin: + description: >- + surface=admin row. `admin_url` appears only with include_admin_url and + must be "" when can_configure is false. + allOf: + - $ref: '#/components/schemas/StreamSummaryBase' + - type: object + properties: + admin_url: { type: string, maxLength: 511 } + if: + required: [can_configure] + properties: + can_configure: { const: false } + then: + properties: + admin_url: { $ref: './common.yaml#/components/schemas/EmptyString' } + + StreamSummaryLive: + description: surface=live row. Never carries admin_url. + allOf: + - $ref: '#/components/schemas/StreamSummaryBase' + - type: object + not: + required: [admin_url] + required: + - streaming_enabled + - playback_transport + - eptz_config + - detection_model + - privacy_mode + - can_control_privacy + - has_sub_stream + - ptz_enabled + - backchannel_enabled + - go2rtc_hls_available + properties: + streaming_enabled: { type: boolean } + playback_transport: { $ref: './common.yaml#/components/schemas/PlaybackTransport' } + eptz_config: { $ref: '#/components/schemas/EptzConfigString' } + detection_model: { type: string, maxLength: 511 } + privacy_mode: { type: boolean } + can_control_privacy: + type: boolean + description: Equals can_configure for the row. + has_sub_stream: { type: boolean } + ptz_enabled: { type: boolean } + backchannel_enabled: { type: boolean } + go2rtc_hls_available: { type: boolean } + + SummaryPageMeta: + type: object + required: [total, page, page_size, total_pages] + properties: + total: { type: integer, minimum: 0, description: Rows visible to the caller after filtering. } + page: { type: integer, minimum: 1, maximum: 1000000 } + page_size: { type: integer, minimum: 1, maximum: 100 } + total_pages: { type: integer, minimum: 0, description: '0 when total is 0, else ceil(total / page_size).' } + + StreamSummaryPageAdmin: + allOf: + - $ref: '#/components/schemas/SummaryPageMeta' + - type: object + required: [streams] + properties: + streams: + type: array + maxItems: 100 + items: { $ref: '#/components/schemas/StreamSummaryAdmin' } + + StreamSummaryPageLive: + allOf: + - $ref: '#/components/schemas/SummaryPageMeta' + - type: object + required: [streams] + properties: + streams: + type: array + maxItems: 100 + items: { $ref: '#/components/schemas/StreamSummaryLive' } + + StreamSummaryPageLpr: + allOf: + - $ref: '#/components/schemas/SummaryPageMeta' + - type: object + required: [streams] + properties: + streams: + type: array + maxItems: 100 + items: { $ref: '#/components/schemas/StreamSummaryLpr' } + + LegacyStreamList: + description: >- + Out of pilot (inventoried, not contract-tested by T18): GET /api/streams + without summary. Bare unbounded array of the same objects as the /full + detail, each redacted per camera. + x-lightnvr-pilot: false + type: array + items: { $ref: '#/components/schemas/StreamDetail' } + + # ------------------------------------------------------------------ + # Full detail (GET /api/streams/{name}/full) + # ------------------------------------------------------------------ + DetailStatus: + type: string + enum: [Running, Degraded, Error, Stopped, Unknown, Starting, Stopping, Reconnecting] + description: >- + Fleet health first; when health is unknown and the stream is enabled, + the stream-state/UDT status is used (adds Starting/Stopping/Reconnecting). + x-lightnvr-source: src/web/api_handlers_streams_get.c:summary_status,resolve_effective_stream_status + + EptzConfigString: + description: >- + "" (disabled) or JSON text of a version-1 equidistant ceiling dewarp + document, at most 1023 bytes. The server stores it opaquely after + validating; the document schema is EptzConfigV1. + type: string + maxLength: 1023 + contentMediaType: application/json + contentSchema: { $ref: '#/components/schemas/EptzConfigV1' } + x-lightnvr-max-bytes: 1023 + x-lightnvr-source: src/web/api_handlers_streams_modify.c:copy_valid_eptz_config; include/core/config.h:EPTZ_CONFIG_MAX + + EptzConfigV1: + type: object + required: [version, projection, mount, centerX, centerY, radius, fov, rotation, defaultYaw, defaultTilt, defaultViewFov] + properties: + version: { const: 1 } + projection: { const: equidistant } + mount: { const: ceiling } + centerX: { type: number, minimum: 0, maximum: 1 } + centerY: { type: number, minimum: 0, maximum: 1 } + radius: { type: number, minimum: 0.05, maximum: 1 } + fov: { type: number, minimum: 160, maximum: 360 } + rotation: { type: number, minimum: -360, maximum: 360 } + defaultYaw: { type: number, minimum: -360, maximum: 360 } + defaultTilt: { type: number, minimum: -90, maximum: 30 } + defaultViewFov: { type: number, minimum: 20, maximum: 120 } + + StreamDetailBase: + description: >- + Non-sensitive fields of the full stream object, emitted for every caller + with live.view. Credential-bearing keys (url, onvif_username, + onvif_password, admin_url, go2rtc_source_override, sub_stream_url, + detection_url, publish_url) are declared here only as strings and are + NOT required; each variant pins them. + type: object + required: + - camera_uuid + - location_uuid + - name + - enabled + - streaming_enabled + - playback_transport + - eptz_config + - width + - height + - fps + - codec + - priority + - record + - segment_duration + - detection_based_recording + - detection_model + - detection_threshold + - detection_interval + - pre_detection_buffer + - post_detection_buffer + - detection_object_filter + - detection_object_filter_list + - protocol + - record_audio + - audio_voice_enhancement + - isOnvif + - backchannel_enabled + - retention_days + - detection_retention_days + - max_storage_mb + - tier_critical_multiplier + - tier_important_multiplier + - tier_ephemeral_multiplier + - storage_priority + - ptz_enabled + - ptz_max_x + - ptz_max_y + - ptz_max_z + - ptz_has_home + - onvif_profile + - onvif_port + - record_on_schedule + - recording_schedule + - detection_record_on_schedule + - detection_recording_schedule + - tags + - privacy_mode + - can_control_privacy + - motion_trigger_source + - has_sub_stream + - status + - go2rtc_hls_available + properties: + camera_uuid: { $ref: './common.yaml#/components/schemas/CameraUuid' } + location_uuid: { $ref: './common.yaml#/components/schemas/CameraUuid' } + name: { $ref: './common.yaml#/components/schemas/StreamName' } + url: { type: string, maxLength: 511 } + enabled: { type: boolean } + streaming_enabled: { type: boolean } + playback_transport: { $ref: './common.yaml#/components/schemas/PlaybackTransport' } + eptz_config: { $ref: '#/components/schemas/EptzConfigString' } + width: { $ref: './common.yaml#/components/schemas/Int32' } + height: { $ref: './common.yaml#/components/schemas/Int32' } + fps: { $ref: './common.yaml#/components/schemas/Int32' } + codec: { type: string, maxLength: 15 } + priority: { $ref: './common.yaml#/components/schemas/Int32' } + record: { type: boolean } + segment_duration: { $ref: './common.yaml#/components/schemas/Int32' } + detection_based_recording: { type: boolean } + detection_model: { type: string, maxLength: 511 } + detection_threshold: + type: integer + description: >- + Integer percent: (int)(stored_float * 100.0f), truncated toward + zero. Not a 0..1 float on the wire. + detection_interval: { $ref: './common.yaml#/components/schemas/Int32' } + pre_detection_buffer: { $ref: './common.yaml#/components/schemas/Int32' } + post_detection_buffer: { $ref: './common.yaml#/components/schemas/Int32' } + detection_object_filter: { type: string, maxLength: 15 } + detection_object_filter_list: { type: string, maxLength: 255 } + protocol: { $ref: './common.yaml#/components/schemas/StreamProtocol' } + record_audio: { type: boolean } + audio_voice_enhancement: { type: boolean } + isOnvif: { type: boolean } + backchannel_enabled: { type: boolean } + retention_days: { $ref: './common.yaml#/components/schemas/RetentionDays' } + detection_retention_days: { $ref: './common.yaml#/components/schemas/RetentionDays' } + max_storage_mb: + allOf: [{ $ref: './common.yaml#/components/schemas/Int32' }] + description: Quota in MB; 0 = unlimited. + tier_critical_multiplier: { type: number } + tier_important_multiplier: { type: number } + tier_ephemeral_multiplier: { type: number } + storage_priority: { $ref: './common.yaml#/components/schemas/Int32' } + ptz_enabled: { type: boolean } + ptz_max_x: { $ref: './common.yaml#/components/schemas/Int32' } + ptz_max_y: { $ref: './common.yaml#/components/schemas/Int32' } + ptz_max_z: { $ref: './common.yaml#/components/schemas/Int32' } + ptz_has_home: { type: boolean } + onvif_username: { type: string, maxLength: 63 } + onvif_password: { type: string, maxLength: 63 } + onvif_profile: { type: string, maxLength: 63 } + onvif_port: + allOf: [{ $ref: './common.yaml#/components/schemas/Int32' }] + description: 0 = derive from the stream URL. + record_on_schedule: { type: boolean } + recording_schedule: { $ref: './common.yaml#/components/schemas/HourOfWeekSchedule' } + detection_record_on_schedule: { type: boolean } + detection_recording_schedule: { $ref: './common.yaml#/components/schemas/HourOfWeekSchedule' } + tags: { type: string, maxLength: 255 } + admin_url: { type: string, maxLength: 511 } + privacy_mode: { type: boolean } + can_control_privacy: { type: boolean } + motion_trigger_source: { type: string, maxLength: 255 } + go2rtc_source_override: { type: string, maxLength: 8191 } + sub_stream_url: { type: string, maxLength: 511 } + has_sub_stream: + type: boolean + description: Presence of a sub-stream; visible to viewers by design. + detection_url: { type: string, maxLength: 511 } + publish_url: { type: string, maxLength: 511 } + status: { $ref: '#/components/schemas/DetailStatus' } + error_message: + type: string + minLength: 1 + maxLength: 255 + description: Present only when status is Error and a cause was recorded (currently model-load failures). + go2rtc_hls_available: { type: boolean } + if: + required: [status] + properties: + status: { not: { const: Error } } + then: + not: + required: [error_message] + x-lightnvr-not-emitted: + - detection_zones (D-02) + - detection_api_url + - id + + StreamDetailConfigurable: + description: >- + Admin edit model: caller has camera.configure on this camera + (can_control_privacy = true). Sensitive fields carry real values. + When separate credentials are in use (isOnvif, or non-empty + onvif_username/onvif_password) the server strips userinfo from `url` + and surfaces it through the onvif_* fields. + allOf: + - $ref: '#/components/schemas/StreamDetailBase' + - type: object + required: [url, onvif_username, onvif_password, admin_url, go2rtc_source_override, sub_stream_url, detection_url, publish_url] + properties: + can_control_privacy: { const: true } + if: + anyOf: + - required: [isOnvif] + properties: { isOnvif: { const: true } } + - required: [onvif_username] + properties: { onvif_username: { minLength: 1 } } + - required: [onvif_password] + properties: { onvif_password: { minLength: 1 } } + then: + properties: + url: { $ref: './common.yaml#/components/schemas/UrlWithoutUserinfo' } + + StreamDetailLiveOnly: + description: >- + Viewer (live.view without camera.configure) model. Every + credential-bearing key is OPTIONAL (a server that stops sending it is + compatible) but, if present, must be redacted: onvif_* and the + administrative URLs must be exactly "", and `url` must be + credential-free (no userinfo, no secret-like query parameter). A leaked + value therefore fails validation. + allOf: + - $ref: '#/components/schemas/StreamDetailBase' + - type: object + properties: + can_control_privacy: { const: false } + url: { $ref: './common.yaml#/components/schemas/ViewerSafeUrl' } + onvif_username: { $ref: './common.yaml#/components/schemas/EmptyString' } + onvif_password: { $ref: './common.yaml#/components/schemas/EmptyString' } + admin_url: { $ref: './common.yaml#/components/schemas/EmptyString' } + go2rtc_source_override: { $ref: './common.yaml#/components/schemas/EmptyString' } + sub_stream_url: { $ref: './common.yaml#/components/schemas/EmptyString' } + detection_url: { $ref: './common.yaml#/components/schemas/EmptyString' } + publish_url: { $ref: './common.yaml#/components/schemas/EmptyString' } + not: + required: [detection_api_url] + + StreamDetail: + description: Either variant, selected by can_control_privacy (mutually exclusive by const). + oneOf: + - $ref: '#/components/schemas/StreamDetailConfigurable' + - $ref: '#/components/schemas/StreamDetailLiveOnly' + + StreamFullResponse: + type: object + required: [stream] + properties: + stream: { $ref: '#/components/schemas/StreamDetail' } + + StreamFullResponseConfigurable: + type: object + required: [stream] + properties: + stream: { $ref: '#/components/schemas/StreamDetailConfigurable' } + + StreamFullResponseLiveOnly: + type: object + required: [stream] + properties: + stream: { $ref: '#/components/schemas/StreamDetailLiveOnly' } + + # ------------------------------------------------------------------ + # Update (PUT /api/streams/{name}) + # ------------------------------------------------------------------ + NullableClearableString: + description: Clearable text field. JSON null clears the stored value (same as ""). + type: [string, 'null'] + + DetectionUrl: + description: >- + "" (use main stream) or a URL whose scheme is http, https, rtsp or rtsps + (case-insensitive prefix check). Other schemes: 400. >= 512 bytes: 413 + (scheme is checked first). null clears. + type: [string, 'null'] + maxLength: 511 + pattern: '^($|([Hh][Tt][Tt][Pp][Ss]?|[Rr][Tt][Ss][Pp][Ss]?)://)' + x-lightnvr-max-bytes: 511 + x-lightnvr-overflow: 413 + x-lightnvr-source: src/web/api_handlers_streams_modify.c:is_allowed_detection_url + + PublishUrlList: + description: >- + "" or a comma/whitespace-separated list of rtmp:// or rtmps:// + destinations. Each destination is printable ASCII without quotes or + backslash and has at least one character after the scheme. Invalid + list: 400; whole string >= 512 bytes: 413 (validity is checked first). + null clears. + type: [string, 'null'] + maxLength: 511 + pattern: '^[\t\n\v\f\r ,]*(([Rr][Tt][Mm][Pp][Ss]?://[!#-&(-+\x2D-\x5B\x5D-~]+)([\t\n\v\f\r ,]+[Rr][Tt][Mm][Pp][Ss]?://[!#-&(-+\x2D-\x5B\x5D-~]+)*)?[\t\n\v\f\r ,]*$' + x-lightnvr-max-bytes: 511 + x-lightnvr-overflow: 413 + x-lightnvr-source: src/core/url_utils.c:publish_url_list_is_valid + + StreamUpdateRequest: + description: >- + Edit-form body. All keys optional (partial update). Closed: the server + ignores unknown keys (including `name` and the UI's `is_deleted`, D-01) + but the contract rejects them so client drift is visible. + Wrong-typed values are silently ignored by the server (F-T11-05). + Top-level numeric fields have no server-side range checks except + retention (F-T11-06); the JSON embedded in eptz_config is separately + range-checked. Over-long strings are silently truncated unless + `x-lightnvr-overflow` says otherwise (F-T11-14). + type: object + additionalProperties: false + properties: + url: + type: string + maxLength: 511 + x-lightnvr-overflow: truncate + description: Not validated (an empty string is accepted). A change requires a stream restart. + enabled: { type: boolean } + enable_disabled: + type: boolean + x-lightnvr-pilot: false + description: >- + Out of pilot (enable/bulk-enable). `true` re-enables a disabled + stream synchronously, even when the same body also carries set_privacy_mode. + streaming_enabled: { type: boolean } + playback_transport: { $ref: './common.yaml#/components/schemas/PlaybackTransport' } + eptz_config: { $ref: '#/components/schemas/EptzConfigString' } + codec: + type: string + maxLength: 15 + x-lightnvr-overflow: truncate + description: Codec hint; the UI sends "" (auto), "h264" or "h265". Not validated. + priority: + allOf: [{ $ref: './common.yaml#/components/schemas/Int32' }] + x-lightnvr-intended-range: '1..10' + protocol: { $ref: './common.yaml#/components/schemas/StreamProtocol' } + segment_duration: + allOf: [{ $ref: './common.yaml#/components/schemas/Int32' }] + description: Seconds. + record: { type: boolean } + detection_based_recording: { type: boolean } + detection_model: + type: string + maxLength: 255 + x-lightnvr-max-bytes: 255 + x-lightnvr-overflow: truncate + description: Copied through a 256-byte buffer although the stored field holds 511 bytes. + detection_threshold: + type: number + description: Percent; stored as value / 100.0 (float). Not range-checked. + x-lightnvr-intended-range: '0..100' + detection_interval: { $ref: './common.yaml#/components/schemas/Int32' } + pre_detection_buffer: { $ref: './common.yaml#/components/schemas/Int32' } + post_detection_buffer: { $ref: './common.yaml#/components/schemas/Int32' } + detection_object_filter: + type: string + maxLength: 15 + x-lightnvr-overflow: truncate + x-lightnvr-intended-enum: [none, include, exclude] + detection_object_filter_list: + type: string + maxLength: 255 + x-lightnvr-overflow: truncate + record_audio: { type: boolean } + audio_voice_enhancement: { type: boolean } + isOnvif: + type: boolean + description: When omitted the server recomputes it from `url` containing "onvif" (F-T11-11). The UI always sends it. + backchannel_enabled: { type: boolean } + onvif_username: + type: string + maxLength: 63 + x-lightnvr-overflow: truncate + description: Applied only when the effective isOnvif is true; "" clears the stored value (no "unchanged" sentinel). + onvif_password: + type: string + maxLength: 63 + x-lightnvr-overflow: truncate + x-lightnvr-sensitive: true + description: Applied only when the effective isOnvif is true; "" clears the stored value (no "unchanged" sentinel). + onvif_profile: + type: string + maxLength: 63 + x-lightnvr-overflow: truncate + description: Applied only when the effective isOnvif is true. + onvif_port: { $ref: './common.yaml#/components/schemas/Int32' } + retention_days: { $ref: './common.yaml#/components/schemas/RetentionDays' } + detection_retention_days: { $ref: './common.yaml#/components/schemas/RetentionDays' } + max_storage_mb: + allOf: [{ $ref: './common.yaml#/components/schemas/Int32' }] + description: 0 = unlimited. + tier_critical_multiplier: { type: number } + tier_important_multiplier: { type: number } + tier_ephemeral_multiplier: { type: number } + storage_priority: + allOf: [{ $ref: './common.yaml#/components/schemas/Int32' }] + x-lightnvr-intended-range: '1..10 (clamped by the UI only)' + ptz_enabled: { type: boolean } + ptz_max_x: { $ref: './common.yaml#/components/schemas/Int32' } + ptz_max_y: { $ref: './common.yaml#/components/schemas/Int32' } + ptz_max_z: { $ref: './common.yaml#/components/schemas/Int32' } + ptz_has_home: { type: boolean } + record_on_schedule: { type: boolean } + recording_schedule: { $ref: './common.yaml#/components/schemas/HourOfWeekSchedule' } + detection_record_on_schedule: { type: boolean } + detection_recording_schedule: { $ref: './common.yaml#/components/schemas/HourOfWeekSchedule' } + tags: + allOf: [{ $ref: '#/components/schemas/NullableClearableString' }] + maxLength: 255 + x-lightnvr-overflow: truncate + admin_url: + allOf: [{ $ref: '#/components/schemas/NullableClearableString' }] + maxLength: 511 + x-lightnvr-overflow: truncate + motion_trigger_source: + allOf: [{ $ref: '#/components/schemas/NullableClearableString' }] + maxLength: 255 + x-lightnvr-overflow: truncate + description: Another stream's name; existence is not checked. + go2rtc_source_override: + allOf: [{ $ref: '#/components/schemas/NullableClearableString' }] + maxLength: 8191 + x-lightnvr-overflow: truncate + description: Whitespace-only is stored as "". Not YAML-validated by PUT. + sub_stream_url: + allOf: [{ $ref: '#/components/schemas/NullableClearableString' }] + maxLength: 511 + x-lightnvr-overflow: truncate + detection_url: { $ref: '#/components/schemas/DetectionUrl' } + publish_url: { $ref: '#/components/schemas/PublishUrlList' } + + StreamPrivacyModeRequest: + description: >- + Live privacy toggle (used by Live video cells, not the edit form). + Modelled as its own closed body because the server discards every other + edit when this key is present (F-T11-12). + type: object + additionalProperties: false + required: [set_privacy_mode] + properties: + set_privacy_mode: { type: boolean } + + StreamUpdateAccepted: + type: object + required: [success, message] + properties: + success: { const: true } + message: { const: Stream update request accepted and processing } + onvif_status: + type: string + enum: [success, error] + description: Present when the effective isOnvif was true and the inline ONVIF test ran. + onvif_message: + type: string + enum: [ONVIF capabilities detected successfully, Failed to detect ONVIF capabilities] + dependentRequired: + onvif_status: [onvif_message] + onvif_message: [onvif_status] + + StreamPrivacyModeResponse: + description: >- + Always success:true, even if the database write failed (the failure is + only logged; F-T11-12). + type: object + required: [success, privacy_mode] + properties: + success: { const: true } + privacy_mode: { type: boolean } + + # ------------------------------------------------------------------ + # Option dependencies + # ------------------------------------------------------------------ + ZonePoint: + type: object + required: [x, y] + properties: + x: { type: number, description: Normalized 0..1 (not re-validated on read). } + y: { type: number, description: Normalized 0..1 (not re-validated on read). } + + DetectionZone: + type: object + required: [id, stream_name, name, enabled, color, polygon, filter_classes, min_confidence] + properties: + id: { type: string, maxLength: 63 } + stream_name: { $ref: './common.yaml#/components/schemas/StreamName' } + name: { type: string, maxLength: 63 } + enabled: { type: boolean } + color: { type: string, maxLength: 7 } + polygon: + type: array + maxItems: 32 + items: { $ref: '#/components/schemas/ZonePoint' } + filter_classes: { type: string, maxLength: 255 } + min_confidence: { type: number, description: 0 = use the stream default. } + x-lightnvr-source: src/web/api_handlers_zones.c:handle_get_zones; include/database/db_zones.h + + ZonesResponse: + type: object + required: [zones] + properties: + zones: + type: array + maxItems: 16 + items: { $ref: '#/components/schemas/DetectionZone' } + + ClientConfig: + type: object + required: + - go2rtc_enabled + - web_auth_enabled + - demo_mode + - audio_disabled + - auto_disabled + - go2rtc_available + - go2rtc_api_port + - go2rtc_base_path + - webrtc_disabled + - hls_disabled + - mse_disabled + - default_playback_transport + - go2rtc_force_native_hls + - webrtc_connection_timeout_ms + - webrtc_ice_recovery_timeout_ms + - generate_thumbnails + - thumbnails_per_recording + properties: + go2rtc_enabled: { type: boolean } + web_auth_enabled: { type: boolean } + demo_mode: { type: boolean } + audio_disabled: { type: boolean } + auto_disabled: { type: boolean } + go2rtc_available: { type: boolean } + go2rtc_api_port: { $ref: './common.yaml#/components/schemas/Int32' } + go2rtc_base_path: { type: string } + webrtc_disabled: { type: boolean } + hls_disabled: { type: boolean } + mse_disabled: { type: boolean } + default_playback_transport: { $ref: './common.yaml#/components/schemas/PlaybackTransport' } + go2rtc_force_native_hls: { type: boolean } + webrtc_connection_timeout_ms: { $ref: './common.yaml#/components/schemas/Int32' } + webrtc_ice_recovery_timeout_ms: { $ref: './common.yaml#/components/schemas/Int32' } + generate_thumbnails: { type: boolean } + thumbnails_per_recording: { $ref: './common.yaml#/components/schemas/Int32' } + x-lightnvr-note: snake_case only (D-06). + + DetectionModel: + type: object + required: [id, name, path, type, supported] + properties: + id: + type: string + description: Built-ins use motion, api-detection, onvif; file models use the absolute file path. + name: { type: string } + path: { type: string } + type: + type: string + description: Observed values include builtin, api, onvif, sod, sod_realnet, tflite. + supported: { type: boolean } + description: { type: string, description: Built-ins only. } + unsupported_reason: { type: string } + size: { type: number, minimum: 0, description: File size in bytes (file models only). } + if: + required: [supported] + properties: { supported: { const: false } } + then: + required: [unsupported_reason] + + DetectionModelsResponse: + type: object + required: [models, litert_available, sod_available, count] + properties: + models: + type: array + minItems: 3 + items: { $ref: '#/components/schemas/DetectionModel' } + x-lightnvr-builtin-prefix: [motion, api-detection, onvif] + litert_available: { type: boolean } + sod_available: { type: boolean } + count: { type: integer, minimum: 3 } + message: + const: Models directory does not exist + description: Present only when the models directory cannot be opened. + + AuthRole: + type: string + enum: [admin, user, viewer, api, unknown] + x-lightnvr-source: src/database/db_auth.c:db_auth_get_role_name + + AuthVerifyAuthenticated: + type: object + required: [authenticated, id, username, email, role, role_id, is_active, password_change_locked, must_change_password, auth_enabled, auth_timeout_hours, auth_absolute_timeout_hours, trusted_device_days] + properties: + authenticated: { const: true } + id: { type: integer } + username: { type: string, maxLength: 63 } + email: { type: string, maxLength: 127 } + role: { $ref: '#/components/schemas/AuthRole' } + role_id: { type: integer, minimum: 0, maximum: 3 } + is_active: { type: boolean } + password_change_locked: { type: boolean } + must_change_password: { type: boolean } + auth_enabled: { const: true } + auth_timeout_hours: { type: integer } + auth_absolute_timeout_hours: { type: integer } + trusted_device_days: { type: integer } + + AuthVerifyAuthDisabled: + type: object + required: [authenticated, username, role, must_change_password, auth_enabled, auth_timeout_hours, auth_absolute_timeout_hours, trusted_device_days] + properties: + authenticated: { const: true } + username: { const: admin } + role: { const: admin } + must_change_password: { const: false } + auth_enabled: { const: false } + auth_timeout_hours: { type: integer } + auth_absolute_timeout_hours: { type: integer } + trusted_device_days: { type: integer } + + AuthVerifyDemo: + type: object + required: [authenticated, demo_mode, username, role, must_change_password] + properties: + authenticated: { const: false } + demo_mode: { const: true } + username: { const: demo } + role: { const: viewer } + must_change_password: { const: false } + + AuthVerifyResponse: + oneOf: + - $ref: '#/components/schemas/AuthVerifyAuthenticated' + - $ref: '#/components/schemas/AuthVerifyAuthDisabled' + - $ref: '#/components/schemas/AuthVerifyDemo' diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml new file mode 100644 index 000000000..fe505709f --- /dev/null +++ b/docs/api/openapi.yaml @@ -0,0 +1,114 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# Copyright (C) 2025-2026 OpenSensor Engineering and LightNVR contributors +# +# Canonical LightNVR HTTP API contract (OpenAPI 3.1, JSON Schema 2020-12). +# This root document only indexes operations; paths and schemas live in the +# fragments under docs/api/contracts/. Bundle/validate with the tooling from +# plan task T12 (scripts/quality/contracts.mjs); do not hand-edit generated +# outputs. +# +# COVERAGE IS PARTIAL. Only the Streams list/edit pilot and its read-only +# option dependencies are specified. Every other route is inventoried in +# quality/api-inventory.json and is NOT described by this file; absence of an +# operation here says nothing about its behavior. +openapi: 3.1.0 +jsonSchemaDialect: https://spec.openapis.org/oas/3.1/dialect/base +info: + title: LightNVR API + version: 0.1.0-pilot + summary: Streams list/edit pilot contract + description: >- + Specified from the C handlers in src/web (serialization and parsing code + read directly), not from prose documentation. Where the C behavior and the + UI or docs disagree, operation descriptions record the C wire behavior and + the disagreement is recorded as a discrepancy (D-xx in + quality/api-inventory.json) or a finding candidate (F-T11-xx in + quality/evidence/T11.md). Extension keywords (`x-lightnvr-*`) are + annotations: they never change validation results. The schemas additionally + enforce explicit client-input and response-redaction policies: they do not + claim that every currently accepted request or emitted response conforms. + Known violations are recorded in the fixtures and findings, with + expected_server_response separate from schema validity. + + Compatibility policy: response objects are open (unknown additional keys + are compatible) except where a schema states otherwise for redaction + reasons; request bodies are closed. Errors use `{"error": ""}`. + license: + name: GPL-3.0-or-later + identifier: GPL-3.0-or-later +servers: + - url: / + description: Same origin as the web UI. +tags: + - name: streams + description: Streams list and edit pilot. + - name: pilot-option-dependency + description: Read-only endpoints the Streams list/edit UI loads to populate options and gating. +security: + - sessionCookie: [] + - basicAuth: [] + - apiKey: [] + - bearerAuth: [] +x-lightnvr-coverage: + scope: streams-pilot + inventory: quality/api-inventory.json + specified_operations: + - GET /api/streams + - GET /api/streams/{name}/full + - PUT /api/streams/{name} + - GET /api/streams/{name}/zones + - GET /api/client-config + - GET /api/detection/models + - GET /api/auth/verify + inventoried_not_specified: + - GET /api/streams/{name} (unwrapped single stream; same object as /full .stream) + - POST /api/streams + - POST /api/streams/test + - DELETE /api/streams/{name} + - POST /api/streams/{name}/zones + - DELETE /api/streams/{name}/zones + - POST /api/streams/{name}/refresh + - all other routes in quality/api-inventory.json routes[] + partially_specified: + - operation: GET /api/streams + note: the legacy (non-summary) array is schema-described via LegacyStreamList but is out of pilot and not contract-tested. + - operation: PUT /api/streams/{name} + note: enable_disabled and set_privacy_mode are described because they share the operation, but their flows are out of pilot. +paths: + /api/streams: + $ref: './contracts/streams.yaml#/paths/~1api~1streams' + /api/streams/{name}/full: + $ref: './contracts/streams.yaml#/paths/~1api~1streams~1%7Bname%7D~1full' + /api/streams/{name}: + $ref: './contracts/streams.yaml#/paths/~1api~1streams~1%7Bname%7D' + /api/streams/{name}/zones: + $ref: './contracts/streams.yaml#/paths/~1api~1streams~1%7Bname%7D~1zones' + /api/client-config: + $ref: './contracts/streams.yaml#/paths/~1api~1client-config' + /api/detection/models: + $ref: './contracts/streams.yaml#/paths/~1api~1detection~1models' + /api/auth/verify: + $ref: './contracts/streams.yaml#/paths/~1api~1auth~1verify' +components: + securitySchemes: + sessionCookie: + type: apiKey + in: cookie + name: session + description: HttpOnly session cookie set by POST /api/auth/login. + basicAuth: + type: http + scheme: basic + apiKey: + type: apiKey + in: header + name: X-API-Key + description: API token (scoped tokens are accepted by the streams handlers but not by /api/auth/verify). + bearerAuth: + type: http + scheme: bearer + description: API token presented as a bearer credential. + x-lightnvr-anonymous-modes: >- + When web authentication is disabled every request is treated as an admin. + In demo mode an unauthenticated request is treated as a viewer + pseudo-user. Neither mode appears as a security requirement. diff --git a/docs/prd/ENG_01_Safety_Contracts_TypeScript.md b/docs/prd/ENG_01_Safety_Contracts_TypeScript.md new file mode 100644 index 000000000..71b0073c3 --- /dev/null +++ b/docs/prd/ENG_01_Safety_Contracts_TypeScript.md @@ -0,0 +1,423 @@ +# PRD — Security, Quality, and Typed Frontend Foundation + +**Status**: Draft — scoped for implementation; phase acceptance remains open +**Created**: 2026-10-31 +**Baseline**: `main` at `1b8d4332`, after PR #691 merged the Live views +**Owner**: LightNVR maintainers; assign an accountable owner to each delivery slice +**Priority**: Product reliability and maintainability +**Scope**: Security and quality remediation, enforceable CI checks, Preact +TypeScript/TSX adoption, executable API contracts, operational regression +coverage, embedded resource budgets, and targeted C boundary improvements. + +## 1. Problem and intended outcome + +LightNVR needs a stronger foundation for routine development and larger future +refactoring. Existing analysis, tests, and documentation provide a substantial +starting point, but several analysis jobs only report findings, frontend application +code has no dedicated type-check gate, and the API contract is primarily prose. +These gaps make it easier for changes to introduce mismatched data assumptions, +repeat resolved defects, or increase resource costs unnoticed. + +The next investment is to improve the current C backend and Preact frontend. +Every phase must provide independent value to the released product. A future +Rust implementation may reuse the resulting contracts and tests, but this PRD +does not depend on a language migration. + +Preact remains the frontend runtime. TSX adds TypeScript to JSX; it does not +require replacing Preact with React. Type annotations are removed during the +build. Preserving the embedded footprint still requires measuring the emitted +assets, dependencies, and runtime behavior. + +## 2. Current baseline + +The following describes the inspected revision, not completed acceptance for +this program. Current scanner results and GitHub alert counts must be collected +in P0; the presence of a workflow does not establish a clean security backlog. + +| Area | Present on main | Preparation needed | +| --- | --- | --- | +| Live view consolidation | PR #691 replaced the separate WebRTC view with the shared [LiveView](../../web/js/components/preact/LiveView.jsx), supported by [transport URL helpers](../../web/js/utils/live-view-url.js) and [compatibility tests](../../web/tests/liveViewTransportTabs.spec.js) | Preserve this completed refactor and its behavior during TSX adoption | +| Frontend build | [Vite](../../web/vite.config.js) targets Preact directly; application files use JS/JSX; [package scripts](../../web/package.json) have no dedicated type-check command | Add incremental TypeScript configuration, test support, and a required check | +| Frontend lint | [ESLint](../../web/eslint.config.js) targets JS/JSX, reports accessibility rules as warnings, disables hooks rules, and enforces an existing UI-string baseline | Extend coverage to TS/TSX and progressively enforce resolved categories | +| Native analysis | [clang-tidy and cppcheck](../../.github/workflows/static-analysis.yml) intentionally report without blocking; [CodeQL](../../.github/workflows/codeql-analysis.yml) analyzes C/C++ | Triage findings and establish explicit regression and release policies; cover frontend security analysis too | +| Dependency/container analysis | [Trivy](../../.github/workflows/container-scan.yml) reports findings with a zero finding exit code | Inventory shipped dependencies and make actionable findings enforceable | +| API contract | [API documentation](../API.md) describes routes, role-dependent responses, and compatibility behavior; no first-party OpenAPI definition was found in the scoped review | Establish a machine-readable contract and test it against C | +| Tests | C/SQLite tests, [ASan/UBSan](../../.github/workflows/sanitizer.yml), frontend unit tests, and [Playwright API/UI/go2rtc projects](../../playwright.config.ts) exist; the main browser CI step explicitly runs UI shards | Map actual execution to risks; make API contracts and relevant failure scenarios explicit CI jobs | +| Deployment | [Container release matrix](../../.github/workflows/docker-publish.yml) covers amd64, arm64, and arm/v7; Vite has default and modern-only browser build paths | Establish measured budgets for supported profiles and identify any additional embedded targets that remain required | + +## 3. Goals and scope boundaries + +### Goals + +- Resolve actionable security and quality findings and prevent their recurrence. +- Catch incorrect frontend props, API usage, state handling, and null assumptions + before release, with strict checking throughout migrated code. +- Make API compatibility executable and reusable by clients and future backends. +- Preserve recording, playback, authorization, recovery, and upgrade behavior. +- Keep resource costs visible on constrained NVRs and viewing devices. +- Clarify ownership and lifecycle boundaries in C where evidence justifies work. + +### Out of scope + +- A Rust backend, transpilation pipeline, or automatically generated release code. +- Replacing Preact, redesigning the UI, or changing the public API for typing convenience. +- Rewriting FFmpeg, go2rtc, SQLite, or other native dependencies. +- A simultaneous database/configuration format migration or broad framework upgrade. +- Treating scanner silence, a successful build, or a coverage percentage as proof + of security or behavioral equivalence. + +Necessary bug fixes can change incorrect behavior, but must be identified as +such and documented separately from mechanical typing or refactoring changes. + +## 4. Requirements + +### 4.1 Findings inventory, remediation, and enforcement + +**Q1 — Establish one findings inventory.** Collect current CodeQL, clang-tidy, +cppcheck, sanitizer, frontend lint, dependency, and container results. Reconcile +known security and quality issues with those results. Identify production, +optional-feature, build-only, and third-party findings without silently excluding +any category. Keep sensitive vulnerability details in the appropriate private +security channel; public tracking uses sanitized references. + +Each finding records its stable ID or fingerprint, source/tool version, affected +revision and configuration, severity, exposure, owner, disposition, and evidence. +Dispositions are: open, fixed, verified false positive, or time-bounded exception. +An exception requires a reason, owner, mitigation where applicable, and review +date. Expired exceptions fail the policy check. Baseline updates require an +explicit reviewed change and cannot automatically absorb new findings. + +**Q2 — Fix by product impact.** Prioritize reachable security defects and data +loss, then memory/lifetime problems, correctness, accessibility, and maintainability. +Review authorization, credential handling, path/file operations, external input, +and dependency exposure as relevant to actual findings. Use focused regression +tests for behavioral defects. Separate dependency upgrades from unrelated type +conversion and avoid indiscriminate forced upgrades. + +**Q3 — Make progress durable.** Introduce required checks that reject new +actionable findings, and promote cleaned rule categories to blocking. Baseline +comparison must use finding identity rather than total warning counts: removing +one old warning cannot conceal a new one. Scanner crashes, configuration errors, +missing expected reports, and accidentally empty scans are failures, not clean +results. Pin or record tool versions and scanner databases so changes are +explainable without indefinitely freezing security intelligence. + +The release gate requires no unresolved first-party security defects or known +data-loss defects. Remaining upstream/unfixable findings require documented, +current risk acceptance; all other actionable quality findings must be resolved +before final program acceptance. A suppression without supporting evidence is +not resolution. Apply checks to relevant feature configurations, including +production combinations omitted by lean analysis builds. + +**Acceptance:** Every collected finding has an owner and disposition; fixed +categories cannot regress unnoticed; deliberate sample violations and a failed +scanner demonstrably fail the gate; required checks are configured in repository +rules as well as workflow YAML. Capture this enforcement evidence once when +introducing each gate rather than adding tests that merely restate configuration. + +### 4.2 Incremental TypeScript and TSX + +**T1 — Add build-time checks without changing the runtime.** Add a frontend +`tsconfig` using `jsx: react-jsx`, `jsxImportSource: preact`, `strict`, +`noUncheckedIndexedAccess`, `isolatedModules`, and no emitted output from the +type checker. Configure module +resolution and compatibility aliases consistently with Vite. Introduce a +required `typecheck` command running `tsc --noEmit`; Vite's transpilation alone +does not check types. + +Allow JS/JSX alongside TS/TSX during migration. Use targeted JSDoc/checking or +narrow typed adapters at remaining JS boundaries. A migration inventory must +show what is still unchecked; `allowJs` alone does not mean that JS is checked. +New application modules use TS/TSX after the foundation lands. Existing modules +move in coherent feature slices, with their meaningful inputs and outputs typed. + +**T2 — Preserve all existing checks.** Update ESLint parser/configuration, file +globs, UI-string enforcement, hooks rules, Jest discovery and transforms, and +the relevant build/test entrypoints for TS/TSX. Run type checking separately for +first-party TypeScript integration/load tests as well; execution or type stripping +is insufficient. Keep Preact compatibility types aligned with runtime aliases. +Do not adopt a new test runner as a prerequisite. + +**T3 — Make types useful.** Type request helpers and query/mutation wrappers +before broad component conversion. Distinguish API data from editable form state +and persisted browser state. Model pending/success/error states and transport +choices explicitly. Handle optional/redacted values, cancellation, errors, +DOM refs, events, and storage parsing. Restrict explicit `any`, unchecked casts, +non-null assertions, and suppression comments; unavoidable third-party adapters +must be narrow, explained, and tracked. Do not use `@ts-nocheck` to mark a file +as migrated. Enable hooks correctness rules for migrated scopes and expand their +coverage as findings are resolved. + +**Acceptance:** Migrated files and their public boundaries pass strict checking; +incorrect props and response-field use fail the required check; migrated tests +are discovered and run; accessibility and localization checks still apply; no +React runtime is introduced; asset reports satisfy the applicable budgets. +Final scope is all first-party browser application code under `web/js`, with +generated/vendor files identified explicitly. Build configuration may remain JS +if checked, and does not count as an application migration exception. + +### 4.3 Executable API contracts + +**A1 — Create a canonical contract.** Add a first-party OpenAPI 3.1 definition +at the proposed path `docs/api/openapi.yaml`. Select and pin compatible validation +and generation tools in P2. Inventory method/path pairs from actual routing and +frontend usage, then reconcile them with the existing API documentation. Track +coverage explicitly; a partial initial schema must not imply whole-API coverage. + +For each covered operation, specify parameters, content types, request/response +schemas, status/error behavior, authentication, and authorization expectations. +Record null versus absent fields, defaults, enum values, units, timestamp formats, +identifier encoding, numeric limits, and pagination. Model summary/full and +role-dependent response variants separately. Preserve current wire behavior, +including tri-state retention values and redacted camera credentials. Do not +silently change integer representations or add privileged fields to make types +simpler. + +**A2 — Generate shared types and verify behavior.** Generate deterministic TS +request/response types from the schema and consume them through a small client +layer. Generated files are not hand-edited; CI regenerates and checks for drift. +Keep readable documentation aligned with the canonical contract. Run contract +tests against the actual C server, exercising successful, malformed, unauthorized, +forbidden, empty, and role-redacted cases where applicable. Tests assert policy +and side effects as well as response shape. Do not generate expected behavior +solely from the same implementation under test. + +**A3 — Validate external data deliberately.** Treat network/storage input as +untrusted until checked. A cast such as `as Stream` does not validate JSON. +Provide runtime validation at selected client boundaries, beginning with the +pilot's request results and persisted state. Measure the validator's emitted +cost; do not ship an entire schema/compiler runtime merely for type generation. +Server-side input validation and authorization remain authoritative. Define +compatible handling of additional fields and unknown values without hiding +missing required data or parsing errors. + +**Acceptance:** The pilot contract matches real C responses and permissions; +invalid fixtures fail validation; a schema/client mismatch fails CI; compatible +additive changes are supported by policy. Final coverage includes all operations +consumed by the first-party UI, plus an inventory of other supported public API +operations and their schema or explicit protocol-test coverage. Streaming/media +responses need protocol tests rather than pretending their bytes are JSON. + +### 4.4 Operational and compatibility regression coverage + +**R1 — Map risks to executed checks.** Reuse existing C, SQLite, frontend, +Playwright, media, and sanitizer fixtures. Record which job actually executes +each important scenario. Add explicit API-contract execution; UI shards alone +do not establish API-project coverage. Distinguish mocks from real server/media +tests and mark hardware-dependent evidence separately. + +| Scenario | Required observable outcome | Execution lane | +| --- | --- | --- | +| Authentication, permissions, and redaction | Each role receives permitted data/actions; denial produces no unauthorized mutation or credential disclosure | PR contract/API tests | +| Stream edit and async actions | Invalid input leaves configuration intact; pending/error UI is correct; repeated clicks do not duplicate a write | PR API/UI tests | +| Unified Live view | Existing index/HLS URLs, mode overrides, transport flags, filters, variant storage keys, fullscreen and substream behavior remain compatible | PR unit/UI; media smoke tests | +| Disconnect and audio/video recovery | Recording resumes within the fixture's declared deadline; resulting media is playable and timestamps remain within documented expectations | Targeted PR tests and scheduled media runs | +| Segment boundaries and retained recordings | Correct duration/order/metadata; retention protects held files and respects inherited, unlimited, and finite policies | PR C/SQLite tests and scheduled media runs | +| Full/read-only/unavailable storage | Failure is reported, cleanup respects policy, and recovery does not erase protected recordings or corrupt metadata | Isolated fault-injection tests | +| Shutdown, forced termination, and restart | Shutdown respects its deadline; restart reconciles incomplete work; database integrity and committed recordings remain recoverable | PR targeted tests and scheduled recovery runs | +| Upgrade and persisted state | A previous supported release's database, configuration, and browser preferences remain usable; incompatible changes are detected | Upgrade fixtures/release qualification | +| Repeated lifecycle activity | No sanitizer errors, deadlocks, unexplained recording gaps, or sustained unbounded growth in memory, threads, or file descriptors | Scheduled soak on declared target/workload | + +Each failure fixture specifies injected fault, expected state transitions, +deadlines/tolerances, and evidence. Establish numeric limits before using a +scenario as an acceptance gate. Compare playback and media metadata where +appropriate; byte-identical recordings are not required. Use isolated temporary +storage and test streams, never production recordings. Preserve existing +ASan/UBSan coverage and assess a focused race-detection job where dependencies +and platform support permit it. + +**Acceptance:** Required scenarios have passing execution evidence, independent +assertions, and retained diagnostics. Quarantined tests have an owner and expiry; +a mandatory acceptance scenario cannot be counted as passed while quarantined. +Retries expose flakes rather than erasing their history. The recent audio and +recorder recovery tests remain part of the applicable coverage. + +### 4.5 Embedded resource budgets + +**B1 — Establish reproducible baselines before migration.** Record commit, +lockfiles, build flags, architecture, browser/device, stream workload, and cache +state. Cover default and modern-only web builds, the supported release matrix, +and at least one named constrained deployment. Decide whether MIPS or additional +non-container targets remain release requirements and document their real build +and validation path. + +Measure installed web assets (including shipped compressed copies, locales, +fonts, and CSS), compressed first-load JS/CSS by page, lazy-loaded bytes, and +dependencies. Separately measure browser startup/interaction and memory, and +NVR binary size, idle/recording RSS, CPU, file descriptors, and recording gaps. +The NVR serves frontend assets; its memory budget is distinct from the viewing +browser's memory budget. + +**B2 — Enforce agreed budgets.** Check in a machine-readable baseline/budget +manifest with numeric limits for each required profile. Asset-size checks run on +PRs; noisy runtime measurements use a controlled runner, repeated samples, and a +documented comparison method. P0 must fill in the limits and name the target; +later acceptance cannot pass against placeholder budgets. No unreviewed budget +increase is allowed. Intentional increases require measured cost, product reason, +and an explicit manifest change. Type-only conversion is expected to preserve +runtime behavior and should not require a larger dependency footprint. + +**Acceptance:** CI publishes comparable before/after asset measurements; an +oversized change fails the gate; pilot and later migrations remain within the +agreed budgets. Emulation/build success is not recorded as physical-device +performance evidence. + +### 4.6 Targeted C boundary improvements + +**C1 — Document and simplify a bounded responsibility.** Identify ownership, +allocation/free responsibilities, thread affinity, callback lifetime, lock order, +cancellation, and shutdown behavior for selected interfaces. Separate pure +policy decisions from filesystem, database, and media side effects where useful. +Represent valid states/transitions and error outcomes explicitly. Use the +[stream-state interface](../../include/video/stream_state.h) and +[request/response abstraction](../../include/web/request_response.h) as existing +boundaries to assess, not justification for a broad rewrite. + +Choose the first refactor from a demonstrated finding or a heavily changed +responsibility, record its invariants, and add missing behavioral coverage before +changing ownership. Keep schema changes, public API changes, and structural +refactoring in distinct reviewable changes. Pure helpers such as the existing +Linux `/proc` parsers are candidates for bounded cleanup; no Rust port is part +of this phase. + +**Acceptance:** At least one selected boundary has documented invariants, +clear ownership, passing failure-path tests, and evidence that the targeted +problem was removed or the dependency surface reduced. Existing recording and +resource gates pass. Completion is based on this bounded improvement, not an +unmeasurable claim that the entire backend is clean. + +## 5. Delivery phases and dependencies + +Phases express dependencies rather than calendar promises. Security fixes start +as soon as findings are confirmed. TypeScript/contracts can progress while the +existing backlog is remediated; migration does not wait for every style warning. + +| Phase | Concrete work | Depends on | Exit gate | +| --- | --- | --- | --- | +| P0 — Inventory and baseline | Collect findings; map tests/jobs and API operations; record supported profiles and resource limits; preserve the merged Live baseline | Current main | Owned findings inventory, coverage map, measured budget manifest, explicit target list | +| P1 — Remediation and CI policy | Fix security/data-loss defects first; introduce identity-based finding checks; promote cleaned categories; add frontend security coverage and required-check rules | P0 | New violations fail; tooling failures fail; release-blocking findings resolved; remaining work owned | +| P2 — Type and contract foundation | Add strict incremental TS, test/lint support, API schema/toolchain, generated types, and explicit API-contract CI | P0; P1 policy applied to changed scope | Required checks demonstrably reject type/contract drift and preserve existing tests | +| P3 — Streams pilot | Type fetch/query boundaries and one Streams list/edit flow; add schemas, validation, C contract tests, UI states, and asset comparison | P2 and P0 budgets | Complete pilot acceptance below; review actual migration effort before expanding | +| P4 — Frontend rollout | Migrate remaining UI by feature; expand API contracts, hooks/accessibility enforcement, test typing, and localization coverage | P3 | All first-party browser application code typed; required endpoint coverage complete; budgets hold | +| P5 — Reliability and C boundaries | Fill operational coverage gaps; run recovery/upgrade/soak scenarios; implement one justified bounded C refactor | P0 coverage map; applicable P1 gates; contract/behavior tests before affected refactors | Operational matrix accepted; bounded refactor meets C1; supported targets qualified | +| P6 — Readiness review | Reconcile findings, contracts, migration inventory, CI enforcement, resources, and compatibility evidence | P1, P4, P5 | Program acceptance below; any future Rust experiment requires its own scoped decision | + +P5 test infrastructure may begin alongside P1/P2. A C refactor waits for its +relevant behavioral tests, not necessarily the entire frontend rollout. P1 +remediation continues until the final findings gate is satisfied. + +### 5.1 First end-to-end pilot + +Use the existing Streams list and edit workflow. Cover the actual operations +used by [StreamsView](../../web/js/components/preact/StreamsView.jsx), +[StreamCard](../../web/js/components/preact/StreamCard.jsx), and +[StreamConfigModal](../../web/js/components/preact/StreamConfigModal.jsx): +`GET /api/streams`, the detail/full variant used for editing, and +`PUT /api/streams/{name}`. Record the exact variants during P2 routing review. +Do not expand this pilot to deletion, bulk actions, or recording architecture. + +1. Capture existing requests, role-dependent results, edit semantics, and errors + in independent fixtures and tests; resolve inconsistencies explicitly. +2. Define the contract and generate request/response types. Keep redacted list + data separate from privileged edit data and editable form values. +3. Type the relevant fetch/query adapters and async-action interfaces; validate + external data and persist useful error/cancellation information. +4. Convert the pilot components and supporting code to TS/TSX with strict checks. + Keep the route, layout, localization, and Preact runtime behavior stable. +5. Verify empty/loading/error/success states, missing optional fields, malformed + responses, permitted/denied edits, invalid input, and duplicate-submit behavior. +6. Run actual C contract tests, focused UI/unit tests, applicable native tests, + and before/after asset measurements. Record implementation and review effort. + +**Pilot exit:** One real feature works from schema through C response and typed +UI, with no untracked type escapes, policy regressions, or budget overruns. +This validates the migration approach, not recording concurrency or whole-API +compatibility. The rollout must adjust if the pilot exposes oversized generated +clients, ineffective types, unstable tooling, or excessive review cost. + +### 5.2 Suggested reviewable PR sequence + +| Slice | Change | Evidence needed | +| --- | --- | --- | +| 1 | Findings/coverage inventories and resource measurement tooling | Reproducible baseline artifacts and named owners/profiles | +| 2 | Required findings gates and initial security/correctness fixes, split by root cause | Finding disposition, behavioral regression evidence, failed-gate demonstration | +| 3 | TS/TSX infrastructure and lint/test support, with a small existing helper migrated | Working strict check, discovered tests, unchanged runtime output or explained difference | +| 4 | Pilot OpenAPI contract, generation, and tests against C | Operation coverage, negative cases, deterministic generation | +| 5 | Typed fetch/query boundaries and Streams pilot | Full pilot acceptance and resource report | +| 6 onward | Separate feature migrations: auth/shared UI; unified Live; recordings/timeline; settings/system; remaining fleet/event/privacy surfaces | Feature contracts, typed tests, applicable compatibility and budget checks | +| Ongoing | Operational fixtures and bounded C improvements | Scenario evidence before/after each affected refactor | +| Final | Acceptance reconciliation and contributor documentation | All P6 gates met; outstanding exceptions explicitly reviewed | + +The order of feature slices may follow active development, but shared contracts +and adapters precede their consumers. Preserve PR #691's unified component; +do not recreate a second Live implementation to simplify migration. + +## 6. Program acceptance + +- Every finding in the P0 inventory is fixed, a verified false positive, or an + approved current upstream/unfixable exception; no actionable first-party + security, data-loss, or quality finding remains open. +- Required analysis, type, lint, generation-drift, contract, and relevant + regression checks prevent merges on failure; changed CI paths cannot silently + bypass them. Scheduled/release gates have named owners and retained evidence. +- The application migration inventory is complete. Strict type checks cover + first-party browser application code; integration tests have their own check; + generated/vendor exclusions and necessary boundary adapters are explicit. +- UI-consumed API operations have canonical contracts tested against C; + remaining public operations have documented coverage status and appropriate + schema/protocol checks. Authorization and sensitive-field behavior are tested. +- Unified Live compatibility, recording/recovery, retention, persistence, and + upgrade scenarios meet the recorded limits. No required acceptance case is + satisfied solely by mocks, quarantine, or a retry concealing a known defect. +- Default/constrained frontend profiles and supported deployment targets meet + recorded asset/runtime budgets, with physical evidence where performance is + claimed. Required measurements and limits are populated, not deferred. +- The selected C boundary improvement passes its invariants and regression + checks, and contributors can reproduce the applicable commands locally. + +Successful completion does not commit LightNVR to a second backend. A later Rust +proposal must identify a specific responsibility, expected ownership benefit, +compatibility evidence, target constraints, and maintenance cost. These decisions +remain separate from accepting improvements to the current product. + +## 7. Rollout, risks, and decisions + +| Risk or decision | Required handling | +| --- | --- | +| Old findings make every PR red | Establish the reviewed baseline first; block new findings immediately and remove baseline entries as fixed | +| Green CI means a scanner did not actually run | Check report presence/scope and tool errors; verify repository required-check behavior, including path filters | +| TSX becomes cosmetic through `any` or assertions | Review typed boundaries, restrict escapes, and require negative type-check evidence for the pilot | +| Generated types disagree with C | Test real responses and policy; change contracts only with reviewed compatibility intent | +| Validators or compatibility packages inflate the UI | Measure per-page and installed assets; use small generation/validation outputs and retain Preact | +| Existing tooling drops migrated tests or lint rules | Update discovery/transforms/scopes before feature conversion; retain localization and accessibility checks | +| Refactoring changes Live preferences or recording behavior | Preserve the merged compatibility fixtures and add operational assertions before structural changes | +| Noisy performance or flaky media tests block useful work | Use controlled profiles, declared tolerances, retained diagnostics, and owned expiry-bound quarantine; keep acceptance evidence explicit | +| Scope expands into a rewrite | Deliver bounded PRs and the pilot first; exclude language/backend replacement from this program | + +P0 must settle the accountable owners, exact constrained devices, required +architectures/browser profiles, numeric budgets, CI cadence/runner allocation, +and findings exception policy. P2 must settle generation/validation tooling and +the API compatibility policy. These are implementation decisions with named +phase deadlines, not reasons to defer inventory or confirmed security fixes. + +During rollout, existing JS and new TS may coexist. Revert a problematic feature +slice without disabling established security or type gates. Preserve current +database/configuration formats and browser storage keys; any necessary +compatibility change receives its own migration and rollback assessment. + +## 8. Related work and references + +- [Mobile-first foundation](UXD_01_MobileFirstFoundation.md) and + [Live view ergonomics](UXD_02_LiveViewErgonomics.md): preserve shared async, + accessibility, mobile, and playback behavior. +- [Scoped authorization and audit](FLEET_02_Scoped_Authorization_Audit.md): reuse + action/scope semantics in contracts and negative tests. +- [Storage lifecycle](STORAGE_01_Multi_Target_Lifecycle.md) and + [retention policies](PRD_Recording_Retention_Policies.md): define protected + data and cleanup behavior for operational tests. +- [Host and hardware health](OPS_03_Host_Hardware_Health.md): reuse existing + resource signals and fault fixtures. +- [Preact TypeScript guide](https://preactjs.com/guide/v10/typescript/), + [Vite TypeScript checking](https://vite.dev/guide/features.html#typescript), + [TypeScript incremental JS support](https://www.typescriptlang.org/tsconfig/allowJs.html), + and [erased types](https://www.typescriptlang.org/docs/handbook/2/basic-types.html#erased-types). diff --git a/docs/prd/README.md b/docs/prd/README.md index 776ec54d7..1d9a789f6 100644 --- a/docs/prd/README.md +++ b/docs/prd/README.md @@ -10,6 +10,12 @@ or hardware acceptance may still remain where the individual PRD says so. ## Active PRDs +### Engineering foundation + +| PRD | Topic | Status | +| --- | --- | --- | +| [Engineering 01 — Security, Quality, and Typed Frontend Foundation](ENG_01_Safety_Contracts_TypeScript.md) | Findings remediation and CI enforcement, Preact TS/TSX, executable API contracts, operational regression coverage, embedded budgets, and targeted C refactoring | Draft (2026-10-31), based on main after PR #691; phased preparation that benefits the current product independently of any future Rust work | + ### UX / Design | PRD | Topic | Driving issues | diff --git a/engineering-foundation-plan.md b/engineering-foundation-plan.md new file mode 100644 index 000000000..17d7a8aac --- /dev/null +++ b/engineering-foundation-plan.md @@ -0,0 +1,7851 @@ +# Plan: Security, Quality, and Typed Frontend Foundation + +**Generated**: 2026-10-31 +**Mode**: Swarm-ready dependency plan +**Source**: [Engineering foundation PRD](docs/prd/ENG_01_Safety_Contracts_TypeScript.md) +**Inspected baseline**: `main`, `1b8d4332` (PR #691's unified Live view) +**Execution status**: In progress; resumed under user authorization on 2026-10-31. User explicitly kept development-tool exception proposals pending; continue independent work without approving them. T1–T4 and T6 were committed before resumption. Physical-device measurements in T5/T36 are deferred at the user's request; this is not passing qualification evidence. + +## Overview + +Strengthen the existing C/Preact product through enforceable findings checks, +strict TS/TSX, executable API contracts, a Streams list/edit pilot, operational +regressions, resource budgets, and one justified C boundary improvement. + +The foundation and pilot have concrete tasks below. Three discovery gates expand +work that cannot honestly be enumerated yet: actual scanner findings (T7), the +remaining application rollout after measuring the pilot (T25), and the selected +C improvement (T31). These tasks produce **additional executable task records in +this plan**, not permission for a worker to undertake a whole backlog. Their +integration gates cannot run until the new dependencies have been inserted and +completed. P6 is not complete merely because the scaffolding or pilot passes. + +Keep shared manifests, generated outputs, workflow integration, and repository +rules under a local integrator. Parallel workers own isolated implementations or +fixtures. Prefer a maximum of three active workers; the coordinator occupies the +fourth available slot. Do not parallelize tests that share ports or output paths. + +## Assumptions + +- Preserve C, Preact, Vite, Jest, existing public behavior, and stored data. Rust, + React adoption, a new test runner, and a database redesign are outside scope. +- The dirty working tree contains the source PRD/index and unrelated documents, + investigations, `homeassistant/`, and other local files. Workers are not alone: + edit only assigned files directly, never reset/revert unrelated work, and report + changed paths. No commits, pushes, PR publication, or production deployment are + required unless separately requested. +- Confirm the working revision and changed paths before starting each task; adapt + this plan if main advances. Do not reimplement PR #691 or restore WebRTCView. +- Every new path/command named below is a **planned deliverable**, not an existing + feature. Its producer task is identified. Names may change only through a + coordinated plan update covering every consumer. +- P0 records actual findings, targets, workload limits, and measurements. No alert + count, fixed dependency version, or performance result is assumed here. +- Default target coverage starts with amd64, arm64, arm/v7 and both current web + build modes. Additional embedded/MIPS requirements need an explicit target + record. Missing hardware blocks its acceptance evidence, not unrelated work. +- Preserve `web/package.json`'s Node 24 range and installed major versions. Select + compatible pinned additions; do not upgrade Preact/Vite/Babel/Jest incidentally. +- The pilot is list/edit only. `StreamsView.jsx` also handles create/delete, + bulk operations, discovery, and ONVIF: extract a typed feature boundary when + needed rather than making those flows unplanned pilot requirements. +- Existing tests sometimes read literal `.jsx` source paths or bundle modules + with extension-based stubs. Rename planning includes those consumers and the + path-keyed i18n baseline; a successful Vite build is insufficient evidence. + +## Prerequisites + +- Node from `.nvmrc`, npm lockfiles, Python 3, CMake, a C/C++ compiler, and the + dependencies in the current workflows. In particular current CMake requires + FFmpeg development libraries at the versions declared there, SQLite, libuv, + llhttp, curl, TLS dependencies, and initialized FAAC/Unity sources. +- Go, FFmpeg/ffprobe, a built go2rtc, and Playwright Chromium for media/UI runs; + optional feature matrices also need their actual dependencies. Lean builds + cannot qualify omitted production features. +- Read access to GitHub findings/workflow artifacts and dependency/container + reports for T1. Insufficient access is recorded explicitly; an empty accessible + result is not proof that all private findings were inspected. +- Repository administration is needed only to configure/verify required checks + in T38/T35. Prepare a concrete ruleset diff first; use existing authorization + or request the missing authority before changing remote settings. +- A named constrained device and controlled workloads for T5/T36. Keep physical + performance evidence distinct from cross-builds and emulation. +- All integration/upgrade/fault tests use disposable storage and synthetic + streams. No production camera credentials or recordings enter fixtures. + +### Shared ownership and validation conventions + +- T8 initially owns both package manifests/lockfiles and shared web configs; + T15, explicit local T25.* integration checkpoints, T26, then T34/T35 own + subsequent coordinated changes. Other workers + submit dependency/config needs to that owner instead of editing shared files. +- T10 owns lint rule compatibility and establishes extension-independent source + identity before conversion. A JSX-to-TSX rename therefore retains the same + allowance without waiting for a later integration task. Never regenerate a + larger i18n allowance or let two implementations share duplicate allowances. +- T12 owns generation tooling and the initial generated output. Later schema + workers edit assigned fragments only. Explicit local T25.* checkpoints + aggregate/generate outputs before dependent consumer tasks begin; T26 performs + final reconciliation after those tasks. Concurrent code generation is prohibited. +- Keep small checked JS re-export shims at old import paths during a migration + wave when necessary. They contain no duplicate implementation, are inventoried, + and T34 removes them or verifies narrowly justified interop exceptions. +- Each task writes evidence to its own `quality/evidence/T.md`; raw reports + go under ignored `build/eng-artifacts/T/`. Evidence must omit secrets. + Only the coordinator updates task statuses/logs and consolidated inventories. +- Run all commands from the repo root. T13 creates `run-native.py` and + `run-integration.mjs` wrappers: they isolate build directories, configurations, + ports, PIDs, database/storage paths, and report directories by task ID, fail + on zero selected tests, and forward subprocess failure codes. Until that is + proven, all server/media runs are serial under the coordinator. +- `npm ci`, Vite builds, code generation, and whole-tree checks need an exclusive + validation lease in a shared checkout. Workers can run isolated tests while + unrelated edits continue, but final evidence comes from a stable integrated + snapshot. Do not attribute another worker's intermediate failure to your task. + +### Mandatory expansion protocol + +T7, T25, and T31 are bounded **planning/integration preparation** tasks. For each: + +1. Append full task records in the format below with stable IDs: T7.1, T7.2, etc.; + T25.1, etc.; T31.1, etc. Give every task exact paths, dependencies, assertions, + and root-runnable validation. Split a fix by root cause and a migration by + a small component/helper cluster; normally no more than six application + implementation files per worker. A single large view may be its own task. +2. Assign related tests, schema fragments, and all extension-sensitive consumers. + Put shared imports, schemas, generation, and manifests in explicit local + integration tasks. Serialize overlaps with existing tasks, including pilot + files and native operational tests. +3. Update the dependency graph and wave table. Add every leaf/integration task + to the designated gate's `depends_on`: T33 for T7, T26 for T25, T32 for T31. + Validate all IDs, acyclicity, ownership, artifact producers, and resource + leases **before marking the expansion task completed**. +4. Execute the resulting tasks under the later executor's authorization. A gate + refuses completion when expansion records are absent, unresolved, or merely + marked skipped. Zero remediation tasks is valid only with complete evidence + that no actionable findings remain. T25 must account for every remaining file. + +This makes unknown work explicit without assigning an unbounded rewrite to one +agent or letting a nominally completed planning task satisfy implementation. +If T9 discovers substantial pre-existing test typing work, apply the same rules +to T9.* tasks and insert their terminal IDs into both T13 and T15 before marking +T9 complete. The initial producer T8 must already provide the test-check command. + +## Dependency Graph + +```text +T1 findings ──> T6 policy ──> T14 scanners ────────────────┐ +T1,T2,T3,T4 ─> T7 expand fixes ──> [T7.*] ──> T33 ──────┤ +T2 inventory ──> T11 contract ──┐ │ +T2,T4,T11 ───> T8 toolchain ───┼─> T12 generation ──┐ │ + ├─> T9 test typing ──┐ │ │ + └─> T10 lint ────────┤ │ │ +T3 coverage ───────────────────> T13 harness ┤ │ │ +T4 assets ──> T5 device budgets └─> T15 foundation +T15 ──> T16 fetch ──> T19 query ──┬─> T20 form ──┐ + ├─> T17 async primitives ─────┤ ├─> T22 pilot wiring + └─> T18 C contracts ──────────┴─> T21 card ──┘ │ + T23 UI ──> T24 pilot gate +T18 ──> T39 contract CI ──> T38 required-check enforcement ──> T24 +T24 ──> T25 expand rollout ──> [T25.*] ──> T26 integration ──> T34 strict closure +T3,T13 ──> T27 media; T28 storage; T29 restart/upgrade ──> T30 soak +T3,T7 ───> T31 expand C boundary ──> [T31.*] ──> T32 integration +T5,T27,T28,T29 also feed T32; T38 also feeds T35 +T30,T32,T33,T34 ──> T35 final CI ──> T36 target qualification ──> T37 acceptance +``` + +The task records below are authoritative where this summary omits a supporting +edge. Bracketed expansion families are not runnable placeholders; their full +records and gate dependencies must be added by the named producer. + +## Tasks + +### T1: Inventory current security and quality findings +- **depends_on**: [] +- **agent_type**: explorer +- **ownership**: `quality/findings.json`, `quality/scanners.json`, `quality/evidence/T1.md`; production code read-only. +- **description**: Collect current analysis/artifacts and known issues; separate application, optional-feature, build-only, third-party, and container findings. Record scanner revision/scope, stable identity, severity/exposure, owner, disposition, evidence, and exception expiry. Mark inaccessible reports as missing. Define the expected scanner-run manifest for T6. +- **acceptance_criteria**: + - No invented clean baseline; each collected item is traceable and each expected scanner has coverage/access status. + - No secrets or private vulnerability details in public files; use sanitized IDs for private records. +- **validation**: + - `python3 -m json.tool quality/findings.json` + - `python3 -m json.tool quality/scanners.json` + - Manually reconcile scanner counts/fingerprints and configurations with source reports. +- **status**: Completed (commit `ff6a7d60`) +- **log**: Non-testable inventory; verified by json.tool and an independent recount from raw reports (CodeQL 34 open+11 dismissed, clang-tidy 296, cppcheck 605, eslint 897, 743 unique fingerprints). 384 itemized findings + 359 buckets; 13 expected scanners with access status. Gaps: secret scanning disabled (404, recorded as not scanned), no JS/TS CodeQL, CodeQL not on PRs, all scanners non-blocking, no required checks, go2rtc/SOD excluded from C analysis, Trivy amd64 only, no CODEOWNERS (placeholder owners). Notable for T7: CodeQL `cpp/sql-injection` (sqlite_migrate.c), uncontrolled allocation (event_router.c), path injection, TOCTOU, cleartext credentials; cppcheck OOB in stream_protocol.c, overflow in config.c; 2 private high-severity advisory drafts need maintainer status. Gotchas: static reports are from `1b8d4332` (+7 line offset after SPDX headers; fingerprints are line-independent); npm audit exits 1 on any finding while C scanners/Trivy force exit 0; workflow artifacts expire 2026-11-30; proposed exception expiry 2027-04-30 needs maintainer confirmation. Raw reports in `build/eng-artifacts/T1/`. +- **files edited/created**: `quality/findings.json`, `quality/scanners.json`, `quality/evidence/T1.md` + +### T2: Map application, API, and rename ownership +- **depends_on**: [] +- **agent_type**: explorer +- **ownership**: `quality/application-inventory.json`, `quality/api-inventory.json`, `quality/evidence/T2.md`; code read-only. +- **description**: Inventory all first-party `web/js` files, imports, entrypoints, tests, source-reading assertions, schema consumers, and shared UI. Enumerate actual C method/path routing and UI usage. Identify Streams summary/full/role variants and list/edit dependencies, including client-config and detection-model options. Record the Live compatibility behavior from PR #691. +- **acceptance_criteria**: + - Every browser file has a proposed domain owner and every UI operation maps to real routing or a recorded discrepancy. + - Explicitly identify shared i18n, auth, toast, modal, ZoneEditor, query, and extension-sensitive test dependencies. +- **validation**: + - `python3 -m json.tool quality/application-inventory.json` + - `python3 -m json.tool quality/api-inventory.json` + - `rg --files web/js web/tests tests/integration` + - Compare inventory completeness to the file list and route registration, not documentation alone. +- **status**: Completed (commit `8d715d7f`) +- **log**: Non-testable inventory; verified by json.tool and a completeness check (302/302 `rg --files` paths, 239/239 C routes from `register_all_libuv_handlers()` in `src/web/libuv_api_handlers.c`, 297/297 UI operations matched via a port of the C matcher). 193 web/js files across 16 domains; 4 unreachable files (`Toast.jsx`, `common/Image.jsx`, `url-param-handler.js`, `utils/dom-utils.js`); 10 discrepancies D-01..D-10. Streams pilot detail in `api-inventory.streams_pilot`: `/full` needs only live-view permission and has configure vs live-only redaction variants; PUT checks permission before body and returns 202; UI sends ignored `is_deleted`, reads unsent `detection_zones`, and reads `error_message`/`playback_transport` absent from admin list; UI gates editing by role while server is per-camera. Extension-sensitive consumers for T8/T10: source-reading specs (streamAvailability→StreamsView.jsx, i18n→Footer.jsx, open-issues-regressions→InvestigationView.jsx), esbuild `.jsx` bundling specs (systemHealth*, liveSlideshowControl, platesView with 6 filename-matched stubs), extension-filtered web/js walkers (browserDialogs, locale-coverage), 48 spec files with explicit-extension imports, Jest `*.spec.js`-only matching, ESLint `.js/.jsx` only, i18n baseline path-keyed. Live (#691): variant/storage prefixes and query keys must stay stable. Gotchas: router is first-match, unmatched `/api/*` → 404 not 405; Playwright `go2rtc` project matches no files; edit form uses raw `fetch` for `/full` and zones; `web/js/version.js` is generated/ignored. +- **files edited/created**: `quality/application-inventory.json`, `quality/api-inventory.json`, `quality/evidence/T2.md` + +### T3: Map operational coverage and reproduce native baseline +- **depends_on**: [] +- **agent_type**: explorer +- **ownership**: `quality/operational-coverage.json`, `quality/native-profiles.json`, `quality/evidence/T3.md`; code read-only. +- **description**: Map PRD scenarios to actual executed tests/jobs. Inspect media recovery, storage, shutdown, upgrade, stream state, and `/proc` seams. Record missing cases, production feature combinations, numerical scenario limits requiring calibration, and candidate bounded C improvements. Archive baseline native binaries/configuration and hashes under `build/eng-baseline/1b8d4332/native/`, with toolchain, dynamic-library/container provenance, and feature flags; record which target each artifact can actually run on. +- **acceptance_criteria**: + - Existing test names/build flags are captured; mocks, real media, and hardware evidence are distinguished. + - Baseline failures are recorded before refactoring; omitted features are not counted as covered. +- **validation**: + - `cmake -S . -B build/eng-T3 -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTS=ON -DENABLE_SOD=OFF -DENABLE_LITERT=OFF -DENABLE_GO2RTC=OFF -DENABLE_MQTT=OFF` + - `cmake --build build/eng-T3 -j2` + - `ctest --test-dir build/eng-T3 --output-on-failure --no-tests=error -R 'test_(system_health_proc|stream_state|shutdown_coordinator|mp4_audio_recovery|mp4_writer_watchdog|storage_retention_sqlite)$'` +- **status**: Completed (commit `561d235c`) +- **log**: Built HEAD `7db92e0d` (compiled sources identical to `8927bc8a`; diff from `1b8d4332` is SPDX comments only), archived under the plan's `1b8d4332` path. As-specified lean Debug: 146/153 pass, targeted 5/6 (`test_mp4_audio_recovery` fails). All 7 failures are a host defect: stale FFmpeg 7.1 headers/.pc in `/usr/local` with libs moved away, so builds compile against 7.1 and link 8.0.1 (avcodec_open2 -22, ASan in av_buffer_unref). Control build with system pkg-config/headers: 153/153 and 6/6 — no code baseline failures. Archives: `debug-lean-as-specified/` (failure repro only), `debug-lean-system-ffmpeg/` (functional/sanitizer baseline), `release-full-system-ffmpeg/` (SOD/LiteRT/go2rtc/MQTT; needs `LD_LIBRARY_PATH=bin/`; approximate T5 reference, not the shipped container). x86_64 only; no arm/MIPS/device evidence. Coverage: 59 registered lean tests never run in CI; go2rtc/SOD tests never run; Playwright CI runs only UI project; no scheduled workflows; recovery tests are mostly link-time mocks. Missing cases recorded for T27 (real disconnect deadlines, post-recovery playability/timestamps, video-only/AAC, retry-exhaustion cooldown, segment metadata), T28 (ENOSPC/EIO, read-only/unmounted, retention modes, protected-file real delete path), T29 (shutdown deadline vs 900 s watchdog, SIGKILL recovery — `storage_reconcile_incomplete_recordings()` untested, upgrade fixtures absent). T31 candidates: stream state transition table; system API `/proc` parsing onto existing collectors (lowest risk); go2rtc_process `/proc` seam; reconcile policy/I-O split; injectable disk-space check. Gotcha for T13: runner must pin FFmpeg paths or fail on header/library version mismatch. +- **files edited/created**: `quality/operational-coverage.json`, `quality/native-profiles.json`, `quality/evidence/T3.md` + +### T4: Measure and gate web asset budgets +- **depends_on**: [] +- **agent_type**: worker +- **ownership**: `scripts/quality/assets.mjs`, `quality/web-budgets.json`, `quality/evidence/T4.md`, `tests/quality/assets.test.mjs`; no manifest/Vite changes. +- **description**: Build the unchanged frontend under an exclusive lease. Implement a dependency-light reporter/checker for default and modern-only outputs: installed bytes including compressed copies/locales, per-entry compressed JS/CSS, shared/lazy chunks, and dependency composition. Archive both outputs, configuration, and hashes under separate `build/eng-baseline/1b8d4332/web-default/` and `web-modern/` directories before subsequent builds overwrite dist. Record exact commands, commit, flags, and justified numeric budgets. +- **acceptance_criteria**: + - Baselines predate TS/toolchain changes; byte counting does not double-count shared chunks or confuse installed/transfer costs. + - Checker rejects missing/stale artifacts and a deliberately oversized fixture; budgets cannot silently self-update. +- **validation**: + - `npm ci --prefix web` + - `node scripts/quality/assets.mjs --capture --profile default` + - `node scripts/quality/assets.mjs --capture --profile modern` + - `node --test tests/quality/assets.test.mjs` +- **status**: Completed (commit `f1ab93b5`) +- **log**: RED (module-not-found, `build/eng-artifacts/T4/red.log`) → GREEN 9/9 node:test (no double-counted shared chunks; missing/stale/wrong-profile/oversized/unknown-budget failures; budgets file unchanged). Real `--check` passes on fresh builds (default 66 budgets, modern 43) and fails on wrong profile, newer source, deleted chunk. Profiles: default = `npm run build` (modern+legacy, 18 locales); modern = `LIGHTNVR_WEB_LEGACY=false`; capture scrubs `LIGHTNVR_WEB_*`/Sentry/sourcemap env. web/ is identical to `1b8d4332` (default captured at `7db92e0d`, modern at `ff6a7d60`; capture refuses if web/ differs). Builds are byte-reproducible. Baselines: installed 11.27 MB default / 7.90 MB modern; served modern JS 604,760 / 584,455; Live first load 451,434 / 425,660. Budgets: totals +3% (min 4 KiB), per-entry +5% (min 2 KiB). Gotchas: no Vite manifest (checker parses HTML/JS refs); web/dist holds one profile at a time, so check right after building that profile and run `npm ci` before building (staleness); no lazy chunks (hls.js in shared Live chunk); ~118 KB unreferenced `css/*.css` copies installed; npm ci skips esbuild/core-js/unrs-resolver install scripts. +- **files edited/created**: `scripts/quality/assets.mjs`, `quality/web-budgets.json`, `quality/evidence/T4.md`, `tests/quality/assets.test.mjs` + +### T5: Establish constrained targets and runtime budgets +- **depends_on**: [T3, T4] +- **agent_type**: local +- **ownership**: `quality/target-profiles.json`, `quality/runtime-budgets.json`, `quality/evidence/T5.md`. +- **description**: Name the constrained NVR and viewing browser, supported architecture/feature profiles, workload, stream fixture parameters, cold/warm cache states, and repeatable measurement method. Use immutable T3/T4 baseline artifacts, or build the pinned baseline in an isolated checkout for additional targets; never measure an evolving checkout or overwritten web/dist as the original baseline. Capture browser and NVR resource baselines separately; settle required MIPS/non-container coverage with available project context or one focused clarification. +- **acceptance_criteria**: + - Numeric limits and tolerances are justified by measurements; no placeholder target or fictitious physical result. + - Missing hardware is an explicit blocker for target acceptance; unrelated development continues. +- **validation**: + - `python3 -m json.tool quality/target-profiles.json` + - `python3 -m json.tool quality/runtime-budgets.json` + - Repeat the recorded workload on the named device/browser and compare samples under the declared method. +- **status**: Inventory/protocol completed (commit `e0544701`); physical calibration deferred by user +- **log**: Nine release target combinations and two immutable web profiles recorded; JSON/invariant checks pass, 12 native archive files and 221/165 web files hash-verified. Three host-only idle runs (180 samples) recorded 55,524–56,272 KiB RSS, 18 FDs, 21 threads and graceful exits; these are not constrained acceptance. Physical NVR/browser targets and 18 runtime limits remain null/unqualified, explicitly deferred by the user. Independent development may consume the protocol; physical-resource acceptance remains open. +- **files edited/created**: `quality/target-profiles.json`, `quality/runtime-budgets.json`, `quality/evidence/T5.md` + +### T6: Implement finding identity and exception policy checks +- **depends_on**: [T1] +- **agent_type**: worker +- **ownership**: `scripts/quality/findings.py`, `tests/quality/test_findings.py`, `tests/quality/fixtures/findings/`, `quality/findings-policy.json`, `quality/evidence/T6.md`. +- **description**: Normalize the inventoried report formats and compare stable findings against reviewed dispositions. Distinguish a valid empty result from missing/crashed/empty-scope scans. Handle exception expiry, tool/database provenance, rename stability, and explicit baseline edits. +- **acceptance_criteria**: + - A new finding fails even when another disappears; stale exceptions and missing expected reports fail. + - Reported scanner failure cannot be converted into success by a pipeline exit-code mistake. +- **validation**: + - `python3 -m unittest discover -s tests/quality -p test_findings.py` + - `python3 scripts/quality/findings.py --check --reports build/eng-artifacts/T1 --inventory quality/findings.json` +- **status**: Completed (commits `29eef189`, `27e4d57e`) +- **log**: RED (`ModuleNotFoundError: findings`) → GREEN 58/58 unittest. Stdlib checker normalizes CodeQL/Trivy SARIF, clang-tidy, cppcheck XML, eslint JSON/job log, npm audit, GitHub Dependabot/Code Quality/advisory/secret-scanning API files, reproducing T1 fingerprints. Identity comparison (new fingerprint or bucket increase fails regardless of disappearances); per-format scan-failure detection (truncated clang-tidy, cppcheck critical errors, npm audit error/zero deps, saved API error bodies, empty scope); exit codes 0 pass / 1 policy / 2 usage / 3 internal (never pass); provenance changes printed; renames only via explicit `path_renames`; `--check` never writes (hash-tested), `--update-baseline --reason` is the sole writer; `--release` ignores gaps and fails needs-triage. Real T1 check exits 0 with all fingerprints matched and 4 accepted, owner/expiry-bound coverage gaps printed as NOT SCANNED (cppcheck partial analysis, secret scanning disabled until 2026-12-31, no JS/TS CodeQL until 2027-01-31, sanitizer log not downloaded until 2026-11-30); without gaps / `--today 2026-12-01` / `--release` it fails truthfully. Gotchas: added `fixed-verified` disposition for T33; gap owners/expiries are proposals needing maintainer confirmation; T14 should emit `run-manifest.json` (files scanned, tool/DB versions) and then set `require_run_manifest: true`; line-independent identity behaves like a per-file count for duplicate diagnostics; eslint log fixture force-added past `*.log` ignore. Raw outputs in `build/eng-artifacts/T6/`. +- **files edited/created**: `scripts/quality/findings.py`, `quality/findings-policy.json`, `tests/quality/test_findings.py`, `tests/quality/fixtures/findings/`, `quality/evidence/T6.md` + +### T7: Materialize atomic remediation tasks +- **depends_on**: [T1, T2, T3, T4] +- **agent_type**: local +- **ownership**: this plan and `quality/remediation-map.json`; production code read-only. +- **description**: Apply the mandatory expansion protocol. Create one T7.* task per root cause, with exact affected files, reproducer, severity priority, and regression check. Separate dependency updates from code fixes. Serialize work overlapping pilot files, C tests, or shared configs. Add local merge/verification tasks and add their terminal IDs to T33. +- **acceptance_criteria**: + - All actionable inventory entries map to executable bounded tasks or reviewed upstream exceptions; critical fixes need not wait for TS migration. + - New tasks have no broad `src/**`/`web/**` ownership. T33 is fenced by explicit dependencies. +- **validation**: + - `python3 -m json.tool quality/remediation-map.json` + - Coordinator checks graph, ownership, evidence producers, and finding-to-task completeness before releasing fix workers. +- **status**: Completed (commits `3eb4d251`, `f9b2f0cf`, `4a390c02`; coordinator integrated expanded records) +- **log**: All 743 fingerprints and 17 T11 candidates mapped to bounded tasks; 335 child records with exact ownership, assertions, dependencies and planned commands. Same-file writers serialized; T33 fenced by every child; pilot/dependency gates updated. Static DAG/coverage checks and eight negative controls pass. No findings approvals or remediation completion inferred from planning. T7.1 already fixed in c285a0c2. +- **files edited/created**: `quality/remediation-map.json`, `quality/evidence/T7.md`, `quality/evidence/T7-expansion.md`, `quality/evidence/T7-check.py`, this plan + +### T8: Establish strict TS, package, and Jest infrastructure +- **depends_on**: [T2, T4, T11] +- **agent_type**: local +- **ownership**: root/web package manifests and lockfiles; web tsconfigs, `web/babel.config.cjs`, `web/jest*.cjs`, `web/eslint.config.js`, `web/vite.config.js` only if needed; `web/tests/types/`; `quality/toolchain.json`. +- **description**: Pin compatible TypeScript, typed ESLint, Babel TS/JSX transforms, test typings, and generation/validation dependencies verified against T11's pilot schema. Put generator/tool dependencies in the root package and web transform/check dependencies in the web package as their import resolution requires. Configure Preact automatic JSX, strict/noUncheckedIndexedAccess/isolatedModules/noEmit and incremental JS coexistence. Register both `npm --prefix web run typecheck` and root `npm run typecheck:tests` now; the latter points to the test config delivered by T9. Add TS/TSX discovery/transforms and a real typed fixture. Preserve Node 24 and existing majors. No empty-project green check. +- **acceptance_criteria**: + - Valid Preact TSX is checked and executed; wrong props fail `tsc`; runtime remains Preact. + - Both Jest unit and Selenium E2E configs discover migrated test extensions; compiler, Babel, and lint peer compatibility is verified. + - New additions are build-time dependencies unless measured runtime use is necessary; remaining JS is explicitly untyped or checked, not silently counted as migrated. +- **validation**: + - `npm ci` and `npm ci --prefix web` (exclusive lease) + - `npm --prefix web run typecheck` + - `npm --prefix web test -- --runInBand` + - `npm --prefix web run build` + - Introduce/remove one temporary invalid prop in the fixture and verify a nonzero type-check exit. +- **status**: Completed (commit `17de8155`) +- **log**: RED missing TSX discovery -> GREEN 55 suites/571 tests; strict Preact fixture checks and executes, deliberate invalid prop fails. Root/web clean installs and peer compatibility verified; both Vite profiles build. Pinned TS5.9.3 and Babel8-compatible transforms without changing installed majors. Root test type command exists but intentionally awaits T9 config. All legacy application JS remains explicitly unchecked. T11 compatibility proof covers 91 fixtures, generated types and browser-bundled Ajv helpers. +- **files edited/created**: root/web package manifests and locks, web Babel/Jest/ESLint configs, web tsconfigs, `web/tests/types/`, `quality/toolchain.json`, `quality/evidence/T8.md` + +### T9: Type-check existing integration and load-test code +- **depends_on**: [T3, T8] +- **agent_type**: worker +- **ownership**: `tests/tsconfig.json`, `tests/load/`, existing `tests/integration/` TS files, `playwright.config.ts`; no packages or new contract specs. +- **description**: Supply the test tsconfig consumed by T8's already-registered root `typecheck:tests` target and inventory existing Playwright/load type errors. Repair a small cluster without broad ambient-any declarations. If repair exceeds that scope, append ordered T9.* tasks using the expansion protocol and add their terminal dependencies to both T13 and T15 rather than treating the entire suite as one worker rewrite. +- **acceptance_criteria**: + - Test code is checked independently of Playwright transpilation and Node type stripping. + - No test behavior or coverage is removed to satisfy types; task evidence lists discovered tests before/after. +- **validation**: + - `npm run typecheck:tests` (script registered by T8) + - `npx playwright test --list` + - Review the load CLI's typed configuration/argument flow without sending load; compiler coverage is the acceptance check for that code here. +- **status**: Completed (commit `b0bcf4c2`) +- **log**: Strict config plus four bounded repair children complete; 38 original diagnostics eliminated across 12 files, all 48 integration/load/config TS files included; 213 test identities unchanged. Three imported browser JS modules explicitly unchecked pending T25/T34. +- **files edited/created**: + +### T10: Preserve lint, hooks, and i18n across extension changes +- **depends_on**: [T8] +- **agent_type**: worker +- **ownership**: `web/eslint.config.js`, `web/eslint-rules/`, `web/i18n-literal-baseline.json`, `tests/quality/lint-compat.test.mjs`, `quality/evidence/T10.md`. +- **description**: Make TS/TSX AST handling and scoped hooks rules work with existing JS. Establish extension-independent baseline identity before migrations; verified JSX-to-TSX renames retain the original counts without waiting for another task. Reject duplicate implementation identities and do not increase allowances. Validate accessibility and untranslated-string detection on TSX as well as JSX. Keep legacy cleanup governed by the findings inventory. +- **acceptance_criteria**: + - A rename does not evade localization rules or produce a fresh larger baseline; typed expression wrappers cannot hide visible literals. + - Migrated scopes enforce hooks correctness and type escape policy while old findings stay explicit. +- **validation**: + - `node --test tests/quality/lint-compat.test.mjs` + - `npm --prefix web run lint` + - Demonstrate failures for one TSX visible string, invalid hooks use, and representative accessibility violation in a clean enforced scope. +- **status**: Completed (commit `4f82c1e2`) +- **log**: Rules and T10.1–T10.3 closure complete: 31 focused checks, 55 suites/571 tests, lint zero errors and unchanged 897 existing warnings. Canonical i18n allowance bytes unchanged; TS hooks/a11y/type escapes and duplicate identity enforced. +- **files edited/created**: + +### T11: Specify the pilot contract from real behavior +- **depends_on**: [T2] +- **agent_type**: worker +- **ownership**: `docs/api/openapi.yaml`, `docs/api/contracts/streams.yaml`, `docs/api/contracts/common.yaml`, `tests/contracts/fixtures/streams/`, `quality/evidence/T11.md`. +- **description**: Define OpenAPI 3.1 operations for list/summary, full edit detail, PUT, and read-only option dependencies identified in T2. Model viewer redaction separately from admin edit data, current errors, nullability, numeric limits, encoded names, schedule values, and retention semantics. Keep out-of-pilot APIs inventoried. Use synthetic manually reviewed fixtures, not only snapshots of current code. +- **acceptance_criteria**: + - Schema matches observed routing and policy; discrepancies become findings rather than undocumented wire changes. + - Sensitive fields cannot be required in viewer models or accidentally accepted as evidence of correct redaction. +- **validation**: + - Review each method/status/model against C handlers and the recorded T2 call graph. + - Contract syntax and fixture validation run in T12 before this artifact becomes a usable generated contract. +- **status**: Completed (commit `a764b6ca`) +- **log**: Recovered abandoned specification and independently verified 178 references, 53 schemas, and 91 fixtures (39 valid/52 invalid). Corrected encoded path suffix boundaries and query parsing descriptions; 15 path cases. Observed C behavior is distinguished from stricter client validation. T12 must enforce byte limits and embedded JSON contentSchema explicitly. T11 findings feed T7. +- **files edited/created**: `docs/api/openapi.yaml`, `docs/api/contracts/{streams,common}.yaml`, `tests/contracts/fixtures/streams/`, `quality/evidence/T11.md` + +### T12: Generate and validate contract artifacts deterministically +- **depends_on**: [T8, T11] +- **agent_type**: worker +- **ownership**: `scripts/quality/contracts.mjs`, `web/js/api/generated/`, `tests/contracts/generated/`, `tests/quality/contracts.test.mjs`, `quality/evidence/T12.md`; package requests go to integrator. +- **description**: Validate/bundle local schema refs; generate runtime-free TS types and compact standalone validators for selected external boundaries using packages already verified/installed by T8. Prefer openapi-typescript and a compatible JSON Schema 2020-12 validator; verify OpenAPI annotations/dialect, role variants, response headers and status handling instead of passing the entire OpenAPI object as a JSON Schema. If an additional package is unavoidable, the coordinator inserts a local package-owner prerequisite before this task; do not wait on downstream T15 or edit manifests concurrently. +- **acceptance_criteria**: + - `--check` generates in a temporary directory and compares bytes, detecting stale/untracked generated outputs without mutating the checkout. + - Known-invalid fixtures fail; permitted extra fields and missing required/redacted data follow explicit policy. Generator/compiler runtime is absent from shipped assets. +- **validation**: + - `node scripts/quality/contracts.mjs --generate` + - `node scripts/quality/contracts.mjs --check` + - `node --test tests/quality/contracts.test.mjs` + - `npm --prefix web run typecheck` +- **status**: Completed (commit `30aecdfc`) +- **log**: RED missing generator -> GREEN 12 isolated tests and all 91 fixture expectations; deterministic read-only drift checks reject missing/untracked/stale outputs and invalid refs. Enforces embedded JSON and UTF-8 byte limits; types/declarations pass strict checking. Per-schema standalone validators tree-shake (client-config 1.6KB gzip, viewer 7KB, update 5.3KB); full metadata stays in test output. No compiler runtime shipped or package changes. +- **files edited/created**: `scripts/quality/contracts.mjs`, `web/js/api/generated/`, `tests/contracts/generated/`, `tests/quality/contracts.test.mjs`, `quality/evidence/T12.md` + +### T13: Isolate native, API, and UI validation runs +- **depends_on**: [T3, T9, T9.4] +- **agent_type**: local +- **ownership**: `scripts/quality/run-native.py`, `scripts/quality/run-integration.mjs`, `tests/integration/global-{setup,teardown}.ts`, shared integration fixtures/configuration, `playwright.config.ts`, `tests/unit/CMakeLists.txt`, `tests/quality/test_runners.py`. +- **description**: Create task-scoped test runners and configure ports/storage/PIDs/reports consistently. Replace current fixed-port assumptions and generated config mismatches, or retain explicit serialization where hardcoding cannot be safely removed. Protect cleanup with run ownership, loopback-only targets, and PID provenance. Add an `eng` test-fragment registration seam for later native tests. Runner builds/binary selection and optional go2rtc fixtures must be explicit. +- **acceptance_criteria**: + - Tests cannot delete another run's files or stop an unrelated service; no server run follows a production LIGHTNVR_URL. + - Concurrent runs pass only after isolation is proven; otherwise the runner enforces a serial lock. No selected tests is failure. +- **validation**: + - `python3 -m unittest discover -s tests/quality -p test_runners.py` + - `node scripts/quality/run-integration.mjs --task T13 --project api --grep @api` + - `python3 scripts/quality/run-native.py --task T13 --regex '^test_system_health_proc$'` + - Prove independent test-data/process ownership and failure propagation under the configured concurrency mode. +- **status**: Runner implementation completed (commit `1de1fa7e`); discovered application/test defects remain explicit downstream tasks +- **log**: 17 ownership/failure tests and strict test types pass; real ASan/UBSan proc test passes. Full API run 43 pass/2 skip/2 stale expectation failures; separate go2rtc smoke exposed actual shutdown deadlock and correctly failed forced teardown. Final lean smoke 4/4 passes with graceful cleanup, not a substitute for go2rtc evidence. T31 fixes reproduced lifecycle defect; T13.1–T13.3 own bounded API corrections. Runtime lock, token/config/exe/starttime checks and pidfd descendant cleanup verified; no unrelated process/file cleanup. Added sorted native fragment seam and pinned FFmpeg ABI probe. +- **files edited/created**: quality native/integration runners, runner tests, owned-process fixtures, global setup/teardown, Playwright results path, native fragment seam, two existing go2rtc fixture path/port repairs, T13 evidence + +### T14: Wire analysis reports and blocking policy into CI +- **depends_on**: [T6, T8, T10, T7.1, T10.3, T14.1] +- **agent_type**: local +- **ownership**: `.github/workflows/static-analysis.yml`, `codeql-analysis.yml`, `container-scan.yml`, `quality/scanners.json` scope extensions, `quality/evidence/T14.md`. +- **description**: Preserve raw-report artifacts while applying the T6 policy. Add frontend JS/TS security coverage and explicit production/development dependency reports. Record scanner exit/report status independently; ensure changed tool/database versions remain visible. Do not mistake CodeQL upload success for an enforced no-new-alert gate; connect actual finding policy or verified repository code-scanning rules. +- **acceptance_criteria**: + - New findings and scanner errors produce required failing statuses; artifact upload still occurs. + - Optional-feature exclusions and production image/architecture scope are visible; valid no-finding scans remain distinguishable from no scan. +- **validation**: + - `python3 -m unittest discover -s tests/quality -p test_findings.py` + - Run CI on an authorized test branch or equivalent isolated runner; attach evidence for clean, new-finding, and scanner-error cases. +- **status**: Implemented locally — hosted scanner execution and full coverage pending (commit `0901a9d2`) +- **log**: Same-run manifests and full aggregate wired;4 workflow,25 scanner subprocess and69 findings tests pass; actionlint passes. Actual hosted extraction/scanning unverified; full gate intentionally fails for5 missing scanner identities and unapproved proposals. No push or remote enforcement. +- **files edited/created**: + +### T15: Integrate foundation checks and required-status policy +- **depends_on**: [T4, T7, T9, T10, T12, T13, T14, T7.8, T7.9, T7.10, T9.4, T10.3, T13.2, T15.1, T15.2, T15.3] +- **agent_type**: local +- **ownership**: shared manifests/lockfiles, `.github/workflows/integration-test.yml`, new engineering workflow if justified, `quality/required-checks.json`, `quality/evidence/T15.md`. +- **description**: Resolve infrastructure changes, finalize command registrations and dependency pins, and add local/CI type/lint/unit/asset/schema statuses with explicit path-filter behavior. T39 activates real pilot contract CI once T18 exists; never allow an empty future suite to report success. Produce the concrete required-status settings for T38, without making remote administrative access a prerequisite for local development. +- **acceptance_criteria**: + - Foundation runs on a stable integrated snapshot, including existing Live compatibility tests and both web build modes. + - Foundation code/CI passes independently of remote administration; T38 owns actual enforcement evidence and fences pilot/final acceptance. +- **validation**: + - `npm --prefix web run typecheck` + - `npm run typecheck:tests` + - `npm --prefix web run lint` + - `npm --prefix web test -- --runInBand` + - `node scripts/quality/contracts.mjs --check` + - `node scripts/quality/assets.mjs --check --profile default` + - `node scripts/quality/assets.mjs --check --profile modern` + - Review the proposed branch/ruleset diff against `quality/required-checks.json`; actual enforcement is validated by T38. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T38: Apply and verify pilot required-check enforcement +- **depends_on**: [T15, T39] +- **agent_type**: local +- **ownership**: reviewed remote required-status configuration, `quality/evidence/T38.md`; no application code. +- **description**: Read current repository rules, apply the concrete type/lint/analysis/assets/schema/real-contract required-check diff with appropriate authority, and verify the actual job identities and path-filter behavior. Preserve unrelated branch protections. Missing authority blocks this task and pilot acceptance while independent implementation can continue. +- **acceptance_criteria**: + - An actual failing required status prevents merge, including the T39 real C contract lane; workflow YAML alone is insufficient evidence. + - No security setting is weakened. T35 must reconcile this evidence again when final statuses are added. +- **validation**: + - Read back branch/ruleset requirements and compare with `quality/required-checks.json`. + - Demonstrate required-status enforcement on an authorized test PR or equivalent repository check evaluation; do not merge deliberately failing code. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T16: Type and validate fetch boundaries +- **depends_on**: [T15] +- **agent_type**: worker +- **ownership**: `web/js/fetch-utils.{js,ts}`, new `web/js/api/http.ts`, `web/tests/fetchUtils.spec.*`, `quality/evidence/T16.md`. +- **description**: Introduce typed HTTP/error/cancellation results and unknown JSON with explicit decoders. Preserve credentials, timeout/retry behavior, and mutations that may have succeeded despite a missing response. Keep a narrow compatibility facade for unconverted consumers; never assert arbitrary JSON as the caller's chosen generic type. +- **acceptance_criteria**: + - Pilot fetches require validation; legacy paths are recorded and cannot silently count as checked. + - Retry, abort listener cleanup, 401/403 handling, malformed bodies, and mutation timeout behavior retain regression coverage. +- **validation**: + - `npm --prefix web run typecheck` + - `npm --prefix web test -- --runInBand --testPathPatterns=fetchUtils` +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T17: Type async UI primitives +- **depends_on**: [T15] +- **agent_type**: worker +- **ownership**: `web/js/hooks/useAsyncAction.{js,ts}`, `web/js/components/preact/AsyncButton.{jsx,tsx}`, `web/tests/useAsyncAction.spec.*`, `quality/evidence/T17.md`. +- **description**: Type action signatures, pending/error states, props and events while preserving double-submit protection, localization, and existing callers through checked compatibility exports. Use the canonical rename identity established by T10 so lint acceptance does not depend on later integration. +- **acceptance_criteria**: + - Promise result/error handling is explicit; unmount/cancellation and repeated invocation are covered without new runtime dependencies. +- **validation**: + - `npm --prefix web run typecheck` + - `npm --prefix web test -- --runInBand --testPathPatterns=useAsyncAction` + - `npm --prefix web run lint` +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T18: Test pilot contracts against the real C backend +- **depends_on**: [T15, T7.334] +- **agent_type**: worker +- **ownership**: `tests/integration/specs/streams-contract.api.spec.ts`, `tests/contracts/fixtures/streams/` test additions, `quality/evidence/T18.md`; C/schema fixes go through owners. +- **description**: Add @contract tests for admin/viewer/denied access, redaction, list/summary/full variants, encoded names, valid edit, rejected edit with unchanged state, and option dependencies. Verify sensitive-field absence independently of permissive schema validation. Use synthetic test accounts/data. +- **acceptance_criteria**: + - Real C responses satisfy the contract and policy; a deliberately wrong fixture/expectation fails. + - Any real defect produces a remediation task; tests/schema are not weakened merely to make it pass. +- **validation**: + - `node scripts/quality/run-integration.mjs --task T18 --project api --grep @contract` + - `npm run typecheck:tests` +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T39: Activate the real pilot contract CI lane +- **depends_on**: [T18] +- **agent_type**: local +- **ownership**: contract job in the T15 engineering/integration workflow, `quality/required-checks.json`, `quality/evidence/T39.md`. +- **description**: Wire T18's actual C contract suite into PR CI, using the isolated runner and explicit build/fixture prerequisites. Ensure contract, handler, schema, and relevant client changes trigger it; update the concrete required-status plan for T38. Keep report upload independent of test success. +- **acceptance_criteria**: + - Real contract assertions and a wrong fixture fail the CI job; absent/zero selected tests cannot pass. + - The lane is ready before pilot acceptance and broad rollout, with actual status names recorded. +- **validation**: + - `node scripts/quality/run-integration.mjs --task T39 --project api --grep @contract` + - Verify CI trigger coverage, output artifacts, nonzero failure propagation, and expected test count on an authorized test branch or isolated equivalent. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T19: Type query/mutation interfaces and pilot API client +- **depends_on**: [T16, T7.334] +- **agent_type**: worker +- **ownership**: `web/js/query-client.{js,ts}`, `web/js/api/streams.ts`, `web/tests/queryClient.spec.ts`, `web/tests/streamsClient.spec.ts`. +- **description**: Preserve both existing query call forms and cache behavior. Type keys, decoder-derived results, mutation input/output, invalidation and cancellation. Use generated pilot types/validators from T12. Isolate any inadequate third-party typings behind a narrow tested adapter, not a module-wide any declaration. +- **acceptance_criteria**: + - Client types distinguish redacted summary data from admin forms; network errors remain typed and observable. + - Existing JS consumers still work; migrated consumers cannot get unchecked generic results. +- **validation**: + - `npm --prefix web run typecheck` + - `npm --prefix web test -- --runInBand --testPathPatterns='queryClient|streamsClient|fetchUtils'` +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T20: Type the edit form and form model +- **depends_on**: [T17, T18, T19, T7.167, T7.177, T7.180, T7.198] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/StreamConfigModal.{jsx,tsx}`, new `web/js/components/preact/streams/streamForm.ts`, `web/tests/streamForm.spec.ts`. +- **description**: Separate editable strings/checkboxes/schedules from wire models. Preserve full-detail loading, secret/redacted-field semantics, tri-state retention, optional values, and unchanged-field submission. Describe any ZoneEditor/legacy child boundary explicitly without assuming unknown props are safe. +- **acceptance_criteria**: + - Validated form conversion handles malformed values and omission semantics; no implicit-any props or destructive defaulting of missing credentials. +- **validation**: + - `npm --prefix web run typecheck` + - `npm --prefix web test -- --runInBand --testPathPatterns=streamForm` + - `npm --prefix web run lint` +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T21: Type stream cards without changing snapshot behavior +- **depends_on**: [T17, T18, T19, T7.161, T7.180, T7.189, T7.193, T7.197] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/StreamCard.{jsx,tsx}`, `web/tests/streamCard.spec.ts`. +- **description**: Type redacted operational data, callbacks, and badge helpers. Preserve bounded snapshot concurrency, cleanup/release, async actions, and mobile markup; edit/delete/bulk behavior outside list/edit remains delegated through explicit callbacks. +- **acceptance_criteria**: + - Missing optional data has defined display behavior and no privileged-field assumption; snapshot slot cancellation/release is tested where relevant. +- **validation**: + - `npm --prefix web run typecheck` + - `npm --prefix web test -- --runInBand --testPathPatterns=streamCard` + - `npm --prefix web run lint` +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T22: Integrate the typed Streams list/edit feature +- **depends_on**: [T20, T21, T7.161, T7.168, T7.180, T7.189, T7.198] +- **agent_type**: local +- **ownership**: `web/js/components/preact/StreamsView.jsx`, new `web/js/components/preact/streams/StreamListEditor.tsx`, `web/js/pages/streams-page.jsx`, pilot import shims. T10's canonical i18n identity already covers renames. +- **description**: Wire the typed API/model/components into the existing page. Extract list/edit orchestration if needed so ONVIF/create/delete/bulk workflows remain separately inventoried. Give cross-boundary values explicit validated shapes; do not label the remaining legacy view fully migrated. Preserve filters, layout, auth, toasts, localization, and pending behavior. +- **acceptance_criteria**: + - List/edit has typed data flow end to end; unrelated flows still function; no duplicate implementation or new public route. +- **validation**: + - `npm --prefix web run typecheck` + - `npm --prefix web test -- --runInBand` + - `npm --prefix web run build` + - `node scripts/quality/contracts.mjs --check` +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T23: Exercise pilot UI and preserved Live behavior +- **depends_on**: [T22] +- **agent_type**: worker +- **ownership**: `tests/integration/specs/streams.ui.spec.ts`, new `tests/integration/specs/streams-typed.ui.spec.ts`, `quality/evidence/T23.md`. +- **description**: Verify loading/empty/error/invalid-data/success states, viewer redaction, denied/permitted editing, failed-edit persistence, cancellation, and duplicate-submit behavior. Use controlled failure responses for UI branches plus real C contract coverage. Run existing Live tests unchanged as a compatibility check. +- **acceptance_criteria**: + - UI state assertions are behavioral; test count does not shrink; accessibility/localization behavior remains intact. +- **validation**: + - `node scripts/quality/run-integration.mjs --task T23 --project ui --grep 'Streams|@streams-typed'` + - `npm --prefix web test -- --runInBand --testPathPatterns='liveViewTransportTabs|liveViewUrl'` + - `npm run typecheck:tests` +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T24: Accept the pilot and measure rollout cost +- **depends_on**: [T5, T23, T38] +- **agent_type**: local +- **ownership**: `quality/pilot-acceptance.json`, `quality/evidence/T24.md`, pilot-only integration corrections. +- **description**: Run the stable pilot against budget/contract/type/UI gates, inspect type escapes and generated size, and record conversion/review effort. If blockers arise, create bounded corrections and add dependencies; do not start broad migration on an unaccepted pilot. +- **acceptance_criteria**: + - PRD pilot acceptance passes with actual C and agreed resource evidence; known unresolved findings affecting this feature block acceptance. + - Document limitations: this says nothing by itself about recorder concurrency or complete API coverage. +- **validation**: + - Repeat T15's frontend checks and T18's contract run on the integrated revision. + - `node scripts/quality/assets.mjs --check --profile default` + - `node scripts/quality/assets.mjs --check --profile modern` + - Run the T5 profile's recorded browser/NVR workload and compare budgets. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T25: Materialize the remaining frontend and API rollout +- **depends_on**: [T2, T24, T7.160, T7.161, T7.162, T7.163, T7.164, T7.165, T7.166, T7.167, T7.168, T7.169, T7.170, T7.171, T7.172, T7.173, T7.174, T7.175, T7.176, T7.177, T7.178, T7.179, T7.180, T7.181, T7.182, T7.183, T7.184, T7.185, T7.186, T7.187, T7.188, T7.189, T7.190, T7.191, T7.192, T7.193, T7.194, T7.195, T7.196, T7.197, T7.198, T7.199, T7.200, T7.201, T7.202, T7.273] +- **agent_type**: local +- **ownership**: this plan, `quality/rollout-map.json`, consolidated application/API inventory updates; code read-only. +- **description**: Apply the mandatory expansion protocol using measured pilot cost. Cover shared auth/i18n/navigation/UI first; then Live transport helpers/cells and the shared Live view; recordings/timeline; settings tabs; system/health; users/audit; fleet; events; investigation/LPR; remaining stream/ONVIF/bulk flows; and every leftover utility/page/test. Split each area into small exact file clusters. Add its schema and real C contract tests before typed consumers. Include raw-source test paths, imports, legacy shims, and non-JSON media protocols. Insert explicit local T25.* schema-aggregation/code-generation/package checkpoints before each dependent consumer wave, with exclusive write ownership; those checkpoints must not depend on downstream T26. Attach all terminal tasks to T26. +- **acceptance_criteria**: + - Every remaining file and UI API operation is covered exactly once, with no catch-all migration worker and no recreated WebRTCView. + - Tasks preserve field/redaction semantics, run type/lint/domain tests and budgets, and serialize actual shared ownership. +- **validation**: + - `python3 -m json.tool quality/rollout-map.json` + - Compare the map against `rg --files web/js` and T2 API inventory; coordinator verifies graph and all shared-write constraints. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T26: Integrate the full typed application and contracts +- **depends_on**: [T25] +- **agent_type**: local +- **ownership**: central OpenAPI/common definitions, generated outputs, shared import/config/manifest integration, `quality/evidence/T26.md`. +- **description**: **Locked until T25 has inserted completed rollout dependencies.** Perform final reconciliation after the local T25.* checkpoints have supplied generated types and integration support to their consumers. Regenerate deterministically and merge any bounded final corrections without expanding worker ownership. Verify all UI operations and inventoried public APIs have contract/protocol coverage as required by the PRD; incomplete endpoints remain blockers rather than schema-less success. +- **acceptance_criteria**: + - Every T25.* implementation/integration task is complete; full UI/API suites pass on one revision with no missing discovered tests or unchecked application files. +- **validation**: + - `npm --prefix web run typecheck` + - `npm run typecheck:tests` + - `npm --prefix web test -- --runInBand` + - `node scripts/quality/contracts.mjs --check` + - `node scripts/quality/run-integration.mjs --task T26 --project api` + - `node scripts/quality/run-integration.mjs --task T26-ui --project ui` +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T27: Close media recovery and segment regression gaps +- **depends_on**: [T3, T13, T27.0, T27.1, T27.2] +- **agent_type**: worker +- **ownership**: `tests/unit/test_mp4_audio_recovery.c`, `test_mp4_recovery_watchdog.c`, `test_mp4_writer_watchdog.c`, `test_mp4_segment_boundaries.c`, `test_mp4_segment_timestamps.c`; `tests/fixtures/eng/media/`, `tests/operational/media/`, native registration fragment `tests/unit/cmake/eng/media.cmake`. +- **description**: Fill only T3's missing assertions for disconnect/audio recovery, segment duration/order/timestamps and playable output, with synthetic FFmpeg sources and declared deadlines. Keep production fixes in separate remediation tasks. Preserve existing recent audio/watchdog coverage. +- **acceptance_criteria**: + - Tests demonstrate actual media validity where claimed; fixture thresholds are explicit and unexpected gaps fail. +- **validation**: + - `python3 scripts/quality/run-native.py --task T27 --regex '^test_mp4_(audio_recovery|recovery_watchdog|writer_watchdog|segment_boundaries|segment_timestamps)$'` + - Run each new real-media scenario using its documented synthetic source and ffprobe assertions; record output diagnostics. +- **status**: Completed (commit `586d8d89`) +- **log**: T27.0 producer plus separate collision/retry identity (3b62c681) and paused-reader reset (586d8d89) close the actual failure cases. Latest native84/84 and both unchanged real-source scenarios pass; declared one-frame overlap preserved. Host functional validation only; no constrained-device claim. +- **files edited/created**: + +### T28: Close storage and retention failure gaps +- **depends_on**: [T3, T13, T28.0, T28.1, T28.2] +- **agent_type**: worker +- **ownership**: `tests/unit/test_storage_retention_sqlite.c`, `test_storage_pressure.c`, `test_storage_pressure_extended.c`, `test_storage_manager_retention.c`; `tests/fixtures/eng/storage/`, `tests/operational/storage/`, `tests/unit/cmake/eng/storage.cmake`. +- **description**: Exercise inherited/unlimited/finite retention, protected files, ENOSPC/read-only/unavailable storage, and recovery with real SQLite where applicable. Use fault injection or disposable constrained filesystems; never fill or remount the host's working filesystem. +- **acceptance_criteria**: + - Rejected cleanup preserves protected media; failure/recovery updates correct metadata; scenario limits and failure injection are reproducible. +- **validation**: + - `python3 scripts/quality/run-native.py --task T28 --regex '^test_storage_(retention_sqlite|pressure|pressure_extended|manager_retention)$'` + - Run isolated storage fault scenarios and inspect database integrity plus retained-file assertions. +- **status**: Completed — declared isolated storage scenarios (commits `0eb7e5aa`, `6d254658`, `e56fc168`, `4deb2b4e`) +- **log**: Final six-target ASan/UBSan run passes73/73 cases: real SQLite FULL/READONLY, retention modes/protected files, unavailable-root/retry recovery, real segment ENOSPC/EIO/EROFS and decodable recovery, bounded writer pause/resume. Missing-root guard cannot detect an unmounted filesystem whose underlying directory remains; target mount guards unchanged. Broader real-media/health/physical qualification remains separately scoped. +- **files edited/created**: + +### T29: Test shutdown, restart, and upgrade continuity +- **depends_on**: [T3, T13, T29.0, T29.1, T29.4, T29.2, T29.3] +- **agent_type**: worker +- **ownership**: `tests/unit/test_shutdown_coordinator.c`, `tests/operational/lifecycle/`, `tests/fixtures/eng/upgrade/`, `tests/unit/cmake/eng/lifecycle.cmake`. +- **description**: Add missing shutdown-deadline, forced-kill recovery, committed/incomplete media reconciliation, previous-supported-release database/config upgrade, and browser preference compatibility evidence. Store provenance for synthetic old-version fixtures. Observe graceful timeout separately from teardown's fallback SIGKILL so a hang cannot appear to pass. +- **acceptance_criteria**: + - Database integrity, preserved recordings, preferences, and expected lifecycle state are checked after restart/upgrade. + - Unsupported downgrade paths are described, not assumed safe or exercised on real state. +- **validation**: + - `python3 scripts/quality/run-native.py --task T29 --regex '^test_shutdown_coordinator$'` + - Execute the recorded lifecycle/upgrade commands against isolated old-version fixtures; verify deadline and persistence assertions independently of process exit. +- **status**: Completed (fixes `922bb288`/`cef412d7`, evidence `25278303`) +- **log**: Original coordinator deadline and real upgrade/restart/crash checks all closed through bounded T29.1/.2/.3/.5 fixes. Actual13/13 passes against schema90 while immutable historical89fixture remains preserved; normal shutdown3843/4446ms, no forced cleanup or lingering owned processes. Host functional evidence only; physical-resource qualification remains deferred. +- **files edited/created**: + +### T30: Define and run repeated lifecycle/resource scenarios +- **depends_on**: [T5, T27, T28, T29, T30.1] +- **agent_type**: worker +- **ownership**: `scripts/quality/soak.py`, `tests/operational/soak/`, `quality/soak-profiles.json`, `quality/evidence/T30.md`. +- **description**: Compose the tested failure cases into bounded soak profiles with declared duration/load/recovery tolerances; sample RSS/CPU/FD/thread counts and recording gaps. Support unattended artifact collection and clear pass/fail; keep physical calibration under T36. Assess a separate race-detection build without combining TSan with ASan/UBSan or hiding incompatible dependencies. +- **acceptance_criteria**: + - Unbounded growth, missed recovery deadlines, deadlocks, sanitizer findings, and unexpected gaps fail; unsupported observations are reported as unavailable. +- **validation**: + - `python3 scripts/quality/soak.py --profile ci --output build/eng-artifacts/T30` + - Exercise a controlled failing recovery/resource fixture to prove the summary cannot report green. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T31: Select and materialize one bounded C improvement +- **depends_on**: [T3, T7] +- **agent_type**: local +- **ownership**: this plan, `quality/c-boundary.json`; production code read-only. +- **description**: Choose one evidenced finding or heavily changed responsibility. Document ownership, allocation/free, thread affinity, callbacks, locks, cancellation and shutdown, or pure-policy/I/O separation as applicable. Apply the expansion protocol: exact precondition tests, one bounded refactor, and local verification tasks; add terminal IDs to T32. Add explicit dependencies from child refactor tasks to the operational/precondition tests they consume, and serialize overlapping test-file ownership. `/proc` is a candidate, not a mandate to change sound code. If no benefit is demonstrated, record the unresolved selection and keep acceptance open. +- **acceptance_criteria**: + - Worker ownership is exact, behavioral tests precede implementation, and shared lifecycle/schema changes are excluded or locally integrated. + - No broad backend rewrite or Rust dependency is introduced. +- **validation**: + - `python3 -m json.tool quality/c-boundary.json` + - Review documented invariants and the added task DAG against actual callers and T3 evidence. +- **status**: Completed — bounded disposal improvement selected and verified by T31.1–T31.3 +- **log**: Source review found global state shutdown destroys one of two initialized mutexes while normal removal destroys both; T13 then reproduced a registry-lock reentry deadlock in the same teardown path. Selected shared private final-disposal helper; exact lifecycle regression, implementation and verification tasks T31.1–T31.3 inserted. No T7 source ownership overlap; runner/test registration follows T13. JSON and graph review pass; no implementation outcome claimed. +- **files edited/created**: `quality/c-boundary.json`, `quality/evidence/T31.md`, this plan + +### T32: Integrate and verify the C boundary improvement +- **depends_on**: [T5, T27, T28, T29, T31, T31.1, T31.2, T31.3] +- **agent_type**: local +- **ownership**: selected boundary integration paths from T31, `quality/evidence/T32.md`; no unrelated native modules. +- **description**: **Locked until completed T31.* dependencies are inserted.** Reconcile the selected refactor, verify evidence of simpler ownership/reduced coupling or removal of the defect, and rerun affected operational/sanitizer checks. +- **acceptance_criteria**: + - Bounded C1 outcome is demonstrated with unchanged public/persistence behavior or a separately documented bug fix; resource limits hold. +- **validation**: + - Run the exact focused native/operational commands recorded by T31 plus its relevant T27–T29 scenarios. + - Reproduce the affected workload under ASan/UBSan and compare its T5 resource profile. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T33: Verify remediation closure and refresh findings evidence +- **depends_on**: [T7, T14, T7.1, T7.2, T7.3, T7.4, T7.5, T7.6, T7.7, T7.8, T7.9, T7.10, T7.11, T7.12, T7.13, T7.14, T7.15, T7.16, T7.17, T7.18, T7.19, T7.20, T7.21, T7.22, T7.23, T7.24, T7.25, T7.26, T7.27, T7.28, T7.29, T7.30, T7.31, T7.32, T7.33, T7.34, T7.35, T7.36, T7.37, T7.38, T7.39, T7.40, T7.41, T7.42, T7.43, T7.44, T7.45, T7.46, T7.47, T7.48, T7.49, T7.50, T7.51, T7.52, T7.53, T7.54, T7.55, T7.56, T7.57, T7.58, T7.59, T7.60, T7.61, T7.62, T7.63, T7.64, T7.65, T7.66, T7.67, T7.68, T7.69, T7.70, T7.71, T7.72, T7.73, T7.74, T7.75, T7.76, T7.77, T7.78, T7.79, T7.80, T7.81, T7.82, T7.83, T7.84, T7.85, T7.86, T7.87, T7.88, T7.89, T7.90, T7.91, T7.92, T7.93, T7.94, T7.95, T7.96, T7.97, T7.98, T7.99, T7.100, T7.101, T7.102, T7.103, T7.104, T7.105, T7.106, T7.107, T7.108, T7.109, T7.110, T7.111, T7.112, T7.113, T7.114, T7.115, T7.116, T7.117, T7.118, T7.119, T7.120, T7.121, T7.122, T7.123, T7.124, T7.125, T7.126, T7.127, T7.128, T7.129, T7.130, T7.131, T7.132, T7.133, T7.134, T7.135, T7.136, T7.137, T7.138, T7.139, T7.140, T7.141, T7.142, T7.143, T7.144, T7.145, T7.146, T7.147, T7.148, T7.149, T7.150, T7.151, T7.152, T7.153, T7.154, T7.155, T7.156, T7.157, T7.158, T7.159, T7.160, T7.161, T7.162, T7.163, T7.164, T7.165, T7.166, T7.167, T7.168, T7.169, T7.170, T7.171, T7.172, T7.173, T7.174, T7.175, T7.176, T7.177, T7.178, T7.179, T7.180, T7.181, T7.182, T7.183, T7.184, T7.185, T7.186, T7.187, T7.188, T7.189, T7.190, T7.191, T7.192, T7.193, T7.194, T7.195, T7.196, T7.197, T7.198, T7.199, T7.200, T7.201, T7.202, T7.203, T7.204, T7.205, T7.206, T7.207, T7.208, T7.209, T7.210, T7.211, T7.212, T7.213, T7.214, T7.215, T7.216, T7.217, T7.218, T7.219, T7.220, T7.221, T7.222, T7.223, T7.224, T7.225, T7.226, T7.227, T7.228, T7.229, T7.230, T7.231, T7.232, T7.233, T7.234, T7.235, T7.236, T7.237, T7.238, T7.239, T7.240, T7.241, T7.242, T7.243, T7.244, T7.245, T7.246, T7.247, T7.248, T7.249, T7.250, T7.251, T7.252, T7.253, T7.254, T7.255, T7.256, T7.257, T7.258, T7.259, T7.260, T7.261, T7.262, T7.263, T7.264, T7.265, T7.266, T7.267, T7.268, T7.269, T7.270, T7.271, T7.272, T7.273, T7.274, T7.275, T7.276, T7.277, T7.278, T7.279, T7.280, T7.281, T7.282, T7.283, T7.284, T7.285, T7.286, T7.287, T7.288, T7.289, T7.290, T7.291, T7.292, T7.293, T7.294, T7.295, T7.296, T7.297, T7.298, T7.299, T7.300, T7.301, T7.302, T7.303, T7.304, T7.305, T7.306, T7.307, T7.308, T7.309, T7.310, T7.311, T7.312, T7.313, T7.314, T7.315, T7.316, T7.317, T7.318, T7.319, T7.320, T7.321, T7.322, T7.323, T7.324, T7.325, T7.326, T7.327, T7.328, T7.329, T7.330, T7.331, T7.332, T7.333, T7.334, T7.335, T38, T13.3, T13.4, T28.1, T28.2, T29.1, T27.1, T29.3, T27.2, T7.2.1, T7.2.2, T7.2.3, T7.2.4, T7.2.5, T7.2.6, T7.2.7, T7.2.8, T7.11.1, T7.53.1, T7.24.1, T7.25.1, T7.24.2, T7.24.3, T7.25.2, T7.26.1, T7.26.2, T7.26.3, T7.25.3, T7.26.4, T7.27.1, T7.27.2, T7.27.3, T7.59.1, T7.172.1, T7.59.2, T7.59.3, T7.23.1, T7.23.2, T7.23.3, T7.168.1, T7.168.2] +- **agent_type**: local +- **ownership**: findings dispositions/inventory, `quality/evidence/T33.md`; production changes only through explicit fix tasks. +- **description**: **Locked until all T7.* terminal dependencies are inserted and complete.** Re-run scanners on the integrated revision. Resolve any new actionable findings through additional atomic tasks and dependencies. Check exceptions against current exposure/upstream state and expiry. Do not replace evidence with bulk suppression or warning-count reduction. +- **acceptance_criteria**: + - PRD release/final findings policy is met with complete expected report coverage; inaccessible required reports or unresolved first-party issues block completion. +- **validation**: + - `python3 scripts/quality/findings.py --check --reports build/eng-artifacts/T33 --inventory quality/findings.json` + - Review original-to-current finding identities, all exception records, and actual required scanner job results. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T34: Close migration escapes and retire temporary bridges +- **depends_on**: [T26] +- **agent_type**: local +- **ownership**: web/root type/lint configs, inventoried import shims and entrypoint references, `quality/application-inventory.json`, `quality/evidence/T34.md`. +- **description**: Verify that every first-party browser implementation is checked, remove temporary duplicate extensions/shims, and tighten final include/exclude policy. Keep only documented checked build/vendor exceptions. Reconcile tests, HTML entrypoints, source-path assertions, i18n baseline keys, and UI-operation coverage. Any substantial missed migration becomes an atomic dependency, not a catch-all cleanup. +- **acceptance_criteria**: + - No unchecked implementation is hidden behind an allowJs flag, excluded directory, broad declaration, or ts-nocheck. + - Strict checks, hooks/accessibility/localization enforcement, generated consistency, and full test discovery hold after bridge removal. +- **validation**: + - `npm --prefix web run typecheck` + - `npm run typecheck:tests` + - `npm --prefix web run lint` + - `npm --prefix web test -- --runInBand` + - `node scripts/quality/contracts.mjs --check` + - Compare final inventory to `rg --files web/js` and inspect all recorded escapes/exclusions. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T35: Integrate complete PR, scheduled, and release qualification gates +- **depends_on**: [T30, T32, T33, T34, T38] +- **agent_type**: local +- **ownership**: workflow/config/manifest integration, native test registration integration, `quality/required-checks.json`, contributor instructions, `quality/evidence/T35.md`. +- **description**: Wire PR type/lint/assets/contract/regression lanes, scheduled media/soak lanes, supported-feature scans, and release upgrade/device evidence. Revalidate repository required-check settings and path-filter behavior. Full integration must include serial/shared resources and optional-feature production coverage, not only lean tests. Keep deployment/publishing outside test commands. +- **acceptance_criteria**: + - All required checks have truthful outcomes and retained diagnostics; scheduled/release failures have owners and cannot be counted as passed by quarantine/retry. + - Local contributor commands reproduce CI checks and the combined system passes on one revision. +- **validation**: + - Run full type/lint/unit/API/UI/go2rtc suites through the isolated runners and the native profiles recorded by T3. + - Run T6 findings, T12 generation, T4 asset checks, T27–T30 operational lanes and upgrade checks against the same revision. + - Verify remote required statuses and intentional failure propagation; missing authority leaves this acceptance gate open. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T36: Qualify supported targets and constrained operation +- **depends_on**: [T5, T35] +- **agent_type**: local +- **ownership**: `quality/evidence/T36.md`, target/runtime evidence records; budget changes require a separate reviewed rationale. +- **description**: Run actual target builds and the agreed physical NVR/browser workload, repeated lifecycle/recording tests, both web profiles, and required production feature combinations. Compare against P0 numbers with identical fixtures, not an easier workload. Confirm prior-release upgrade behavior for supported packages. +- **acceptance_criteria**: + - All required target/feature/resource profiles have passing evidence; unsupported or inaccessible hardware remains explicitly incomplete. + - No performance claim is based solely on an emulator or cross-compiler; no silent budget reset. +- **validation**: + - Execute the exact build/workload commands captured in `quality/target-profiles.json` and `quality/runtime-budgets.json`. + - `node scripts/quality/assets.mjs --check --profile default` + - `node scripts/quality/assets.mjs --check --profile modern` + - `python3 scripts/quality/soak.py --profile constrained --output build/eng-artifacts/T36` +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T37: Reconcile final PRD acceptance and handoff +- **depends_on**: [T36] +- **agent_type**: local +- **ownership**: PRD implementation/acceptance status, `docs/prd/README.md`, `quality/acceptance.md`, this plan's final status/log. +- **description**: Map Q1–Q3, T1–T3, A1–A3, R1, B1–B2, C1 and P0–P6 to completed evidence and exact revision. Include all expanded tasks. Keep deferred/blocked outcomes visible and leave the PRD incomplete if any required gate is missing. Document maintenance commands and open exception review dates. Rust remains a separate decision. +- **acceptance_criteria**: + - Every PRD acceptance item has a passing check or its explicitly permitted current exception; no planning/scaffold task is substituted for implementation. + - Final working-tree/ownership review preserves unrelated changes and identifies precisely what was implemented. +- **validation**: + - `python3 scripts/check_docs_links.py` + - `git diff --check` + - Manually trace every PRD acceptance bullet through the evidence matrix and verify final CI revision/target records agree. +- **status**: Not Completed +- **log**: +- **files edited/created**: + + +## Expanded remediation tasks (T7) + + +T7 → T7.1 approval enforcement → T14; T8 → dependency remediations → T15; T10 → web remediation → overlapping pilot migrations and T25; T13 → native/pilot remediations; T12 + pilot remediations → contract reconciliation → T18/T19; every T7.* → T33. Within these frontiers, depends_on includes each previous writer of the same path. + +| Frontier | Tasks | Resource constraints | +| --- | --- | --- | +| Policy/reviews | Tasks whose only producer is T6/T7/T11; T7.2.1–T7.2.4 afterT13; T7.2.5–T7.2.7 serialize buffer fixes | Reviews do not approve exceptions; advisory regression children use serialized native leases | +| Dependency | kind=dependency after T8 | One package/install/build writer, then T15 | +| Web | kind=web-fix after T10 | Max three workers; unique focused Jest files, exclusive full lint/build | +| Native/pilot | kind=native-fix/pilot-fix after T13 | Shared server/native operations serialized by runner lease; exact CMake fragments | +| Reconciliation | kind=integration after T12 and all pilot fixes | Exclusive generation, followed by T18/T19 | +| Closure | all T7.* then T33 | Fresh complete scanners; unresolved reviews block | + +### T7.1: Reject unapproved findings and coverage exceptions +- **depends_on**: [T7, T6] +- **agent_type**: local +- **ownership**: `scripts/quality/findings.py`, `tests/quality/test_findings.py`, `quality/findings-policy.json`, `quality/evidence/T7.1.md`. +- **priority**: high +- **description**: Require explicit reviewed approval evidence before an exception can affect --check or --release. The existing proposed-by-T1/T6 dates and maintainers/ owners are proposals, not approvals. Preserve diagnostic reporting and the read-only normal check. Do not silently bless old records during schema migration. +- **findings**: policy/integration requirement +- **acceptance_criteria**: + - Tests first prove proposed, missing, expired and malformed approvals fail; an explicitly approved current exception succeeds only in its permitted mode. + - An approval must name an accountable reviewer/owner, date and reason; placeholders do not qualify. --release still rejects coverage gaps. + - Run against actual T1 reports: unapproved proposals must cause policy failure. Hash the inventory before/after --check. +- **validation**: + - `python3 -m unittest discover -s tests/quality -p test_findings.py` + - `python3 scripts/quality/findings.py --check --reports build/eng-artifacts/T1 --inventory quality/findings.json` +- **status**: Completed (commit `c285a0c2`) +- **log**: Coordinator implemented RED -> GREEN approval enforcement: 65 tests pass; real T1 fails 575 inventory and 4 gap approval proposals; no accepted gaps. Metadata validation does not authenticate a human approval; do not fabricate production approval records. +- **files edited/created**: + +### T7.2: Verify disposition and regression evidence for ADVISORY-GHSA-cv53-mq7p-6jp2 +- **depends_on**: [T7, T7.2.1, T7.2.2, T7.2.3, T7.2.4] +- **agent_type**: local +- **ownership**: `quality/reviews/advisory-ghsa-cv53-mq7p-6jp2.json`, `quality/evidence/T7.2.md`. +- **priority**: high +- **description**: Read the source advisory through authorized access; record sanitized affected paths, fixed revision and exact existing regression command without publishing private identifiers or text. Published-version exclusion alone is not regression proof. If a regression or fix is absent, append a bounded child task and add it to T33 before this review can close; inaccessible private evidence leaves this task blocked. +- **findings**: ADVISORY-GHSA-cv53-mq7p-6jp2 +- **acceptance_criteria**: + - Confirm state and affected/current versions at the actual execution date. + - Record reviewer, evidence reference and passing regression, or explicit unresolved blocker; a draft or closed label alone is not a verified fix. + - No private advisory text, IDs or credentials in committed evidence. +- **validation**: + - `python3 -m json.tool quality/reviews/advisory-ghsa-cv53-mq7p-6jp2.json` +- **status**: Completed — current source and exact regression evidence reviewed +- **log**: All four bounded regression children complete:7migration,24buffer,14filesystem and7settings cases. Three buffer fixes plus descriptor initialization resolve observed failures; related native controls pass with2explicit unavailable libyaml controls. No release, fresh scanner, baseline change or exception approval inferred. +- **files edited/created**: + +### T7.3: Verify disposition and regression evidence for ADVISORY-repo-advisory-draft-1 +- **depends_on**: [T7] +- **agent_type**: local +- **ownership**: `quality/reviews/advisory-repo-advisory-draft-1.json`, `quality/evidence/T7.3.md`. +- **priority**: high +- **description**: Read the source advisory through authorized access; record sanitized affected paths, fixed revision and exact existing regression command without publishing private identifiers or text. Published-version exclusion alone is not regression proof. If a regression or fix is absent, append a bounded child task and add it to T33 before this review can close; inaccessible private evidence leaves this task blocked. +- **findings**: ADVISORY-repo-advisory-draft-1 +- **acceptance_criteria**: + - Confirm state and affected/current versions at the actual execution date. + - Record reviewer, evidence reference and passing regression, or explicit unresolved blocker; a draft or closed label alone is not a verified fix. + - No private advisory text, IDs or credentials in committed evidence. +- **validation**: + - `python3 -m json.tool quality/reviews/advisory-repo-advisory-draft-1.json` +- **status**: Completed (commit `73fb07ec`) +- **log**: Authorized read-only source refresh confirms unchanged private report state/body; ancestor fixes verified against actual routing/transport regressions (18Unity cases), plus current real viewer-policy controls for proxy aliases (44Unity cases). No private identifiers/body published, no remote state or baseline changed. +- **files edited/created**: + +### T7.4: Verify disposition and regression evidence for ADVISORY-repo-advisory-closed-1 +- **depends_on**: [T7] +- **agent_type**: local +- **ownership**: `quality/reviews/advisory-repo-advisory-closed-1.json`, `quality/evidence/T7.4.md`. +- **priority**: high +- **description**: Read the source advisory through authorized access; record sanitized affected paths, fixed revision and exact existing regression command without publishing private identifiers or text. Published-version exclusion alone is not regression proof. If a regression or fix is absent, append a bounded child task and add it to T33 before this review can close; inaccessible private evidence leaves this task blocked. +- **findings**: ADVISORY-repo-advisory-closed-1 +- **acceptance_criteria**: + - Confirm state and affected/current versions at the actual execution date. + - Record reviewer, evidence reference and passing regression, or explicit unresolved blocker; a draft or closed label alone is not a verified fix. + - No private advisory text, IDs or credentials in committed evidence. +- **validation**: + - `python3 -m json.tool quality/reviews/advisory-repo-advisory-closed-1.json` +- **status**: Completed (commit `73fb07ec`) +- **log**: Authorized read-only source refresh confirms unchanged private report state/body; ancestor fixes verified against actual routing/transport regressions (18Unity cases), plus current real viewer-policy controls for proxy aliases (44Unity cases). No private identifiers/body published, no remote state or baseline changed. +- **files edited/created**: + +### T7.5: Verify disposition and regression evidence for ADVISORY-repo-advisory-draft-2 +- **depends_on**: [T7] +- **agent_type**: local +- **ownership**: `quality/reviews/advisory-repo-advisory-draft-2.json`, `quality/evidence/T7.5.md`. +- **priority**: high +- **description**: Read the source advisory through authorized access; record sanitized affected paths, fixed revision and exact existing regression command without publishing private identifiers or text. Published-version exclusion alone is not regression proof. If a regression or fix is absent, append a bounded child task and add it to T33 before this review can close; inaccessible private evidence leaves this task blocked. +- **findings**: ADVISORY-repo-advisory-draft-2 +- **acceptance_criteria**: + - Confirm state and affected/current versions at the actual execution date. + - Record reviewer, evidence reference and passing regression, or explicit unresolved blocker; a draft or closed label alone is not a verified fix. + - No private advisory text, IDs or credentials in committed evidence. +- **validation**: + - `python3 -m json.tool quality/reviews/advisory-repo-advisory-draft-2.json` +- **status**: Completed (commit `73fb07ec`) +- **log**: Authorized read-only source refresh confirms unchanged private report state/body; ancestor fixes verified against actual routing/transport regressions (18Unity cases), plus current real viewer-policy controls for proxy aliases (44Unity cases). No private identifiers/body published, no remote state or baseline changed. +- **files edited/created**: + +### T7.6: Verify disposition and regression evidence for ADVISORY-GHSA-rqj3-pch5-rq4f +- **depends_on**: [T7] +- **agent_type**: local +- **ownership**: `quality/reviews/advisory-ghsa-rqj3-pch5-rq4f.json`, `quality/evidence/T7.6.md`. +- **priority**: critical +- **description**: Read the source advisory through authorized access; record sanitized affected paths, fixed revision and exact existing regression command without publishing private identifiers or text. Published-version exclusion alone is not regression proof. If a regression or fix is absent, append a bounded child task and add it to T33 before this review can close; inaccessible private evidence leaves this task blocked. +- **findings**: ADVISORY-GHSA-rqj3-pch5-rq4f +- **acceptance_criteria**: + - Confirm state and affected/current versions at the actual execution date. + - Record reviewer, evidence reference and passing regression, or explicit unresolved blocker; a draft or closed label alone is not a verified fix. + - No private advisory text, IDs or credentials in committed evidence. +- **validation**: + - `python3 -m json.tool quality/reviews/advisory-ghsa-rqj3-pch5-rq4f.json` +- **status**: Completed (commit `4ac385d4`) +- **log**: Pinned bundled Go security16/16 and native configuration6/6 pass; reviewed parent/submodule fixes, loopback defaults and non-root/no-direct-API deployment recipes. Current faststart2/2 also passes. No published release/advisory change, baseline mutation or exception approval. +- **files edited/created**: + +### T7.7: Verify disposition and regression evidence for ADVISORY-GHSA-pvw7-7j8c-qx96 +- **depends_on**: [T7] +- **agent_type**: local +- **ownership**: `quality/reviews/advisory-ghsa-pvw7-7j8c-qx96.json`, `quality/evidence/T7.7.md`. +- **priority**: high +- **description**: Read the source advisory through authorized access; record sanitized affected paths, fixed revision and exact existing regression command without publishing private identifiers or text. Published-version exclusion alone is not regression proof. If a regression or fix is absent, append a bounded child task and add it to T33 before this review can close; inaccessible private evidence leaves this task blocked. +- **findings**: ADVISORY-GHSA-pvw7-7j8c-qx96 +- **acceptance_criteria**: + - Confirm state and affected/current versions at the actual execution date. + - Record reviewer, evidence reference and passing regression, or explicit unresolved blocker; a draft or closed label alone is not a verified fix. + - No private advisory text, IDs or credentials in committed evidence. +- **validation**: + - `python3 -m json.tool quality/reviews/advisory-ghsa-pvw7-7j8c-qx96.json` +- **status**: Completed (commit `22012e65`) +- **log**: Public advisory state/source reviewed; existing fix507bce1c is an ancestor. Integration-profile ASan/UBSan passes3 targets/18tests, including decoded query identity, cache update and delayed worker shutdown. No baseline/approval or published advisory mutation. +- **files edited/created**: + +### T7.8: Remediate glob dependency advisory GHSA-3g3m-2fjr-6pw4 +- **depends_on**: [T7, T8] +- **agent_type**: local +- **ownership**: `web/package.json`, `web/package-lock.json`, `quality/evidence/T7.8.md`. +- **priority**: medium +- **description**: Resolve GHSA-3g3m-2fjr-6pw4 for glob using an actually available compatible patched transitive version or remove the vulnerable dependency path. Inspect npm explain and production/dev reachability. Preserve Node 24 and installed direct major versions. If no compatible fix exists, submit a specific owner/expiry/rationale proposal and keep this task open until explicitly reviewed; do not force an unrelated major upgrade. +- **findings**: NPM-WEB-GHSA-3g3m-2fjr-6pw4-glob +- **acceptance_criteria**: + - npm audit no longer reports the advisory on each original dependency path, or an explicitly reviewed current exception documents the residual exposure. + - Dependency graph, unit tests and both web builds remain compatible; no broad overrides that violate parent ranges. +- **validation**: + - `npm --prefix web explain glob` + - `npm --prefix web audit --json` + - `npm --prefix web audit --omit=dev --json` + - `npm --prefix web test -- --runInBand` + - `npm --prefix web run build` + - `LIGHTNVR_WEB_LEGACY=false npm --prefix web run build` +- **status**: Blocked — review prepared, awaiting compatible fix or maintainer decision +- **log**: Concrete review packet committed in `713b3025`; no package mutation, approval or disposition change. Current development-tool advisory ranges and dependency parents checked; production web audit zero. See task evidence and `quality/reviews/exception-ownership.json`. +- **files edited/created**: + +### T7.9: Remediate minimatch dependency advisory GHSA-g3hv-75wg-m249 +- **depends_on**: [T7, T8, T7.8] +- **agent_type**: local +- **ownership**: `web/package.json`, `web/package-lock.json`, `package.json`, `package-lock.json`, `quality/evidence/T7.9.md`. +- **priority**: medium +- **description**: Resolve GHSA-g3hv-75wg-m249 for minimatch using an actually available compatible patched transitive version or remove the vulnerable dependency path. Inspect npm explain and production/dev reachability. Preserve Node 24 and installed direct major versions. If no compatible fix exists, submit a specific owner/expiry/rationale proposal and keep this task open until explicitly reviewed; do not force an unrelated major upgrade. +- **findings**: NPM-WEB-GHSA-g3hv-75wg-m249-minimatch + T8 adds the root openapi-typescript/redocly dev path for the same minimatch advisory; root manifests are included and T33 must reconcile the new occurrence without automatic baseline changes. +- **acceptance_criteria**: + - npm audit no longer reports the advisory on each original dependency path, or an explicitly reviewed current exception documents the residual exposure. + - Dependency graph, unit tests and both web builds remain compatible; no broad overrides that violate parent ranges. +- **validation**: + - `npm --prefix web explain minimatch` + - `npm --prefix web audit --json` + - `npm --prefix web audit --omit=dev --json` + - `npm --prefix web test -- --runInBand` + - `npm --prefix web run build` + - `LIGHTNVR_WEB_LEGACY=false npm --prefix web run build` + - `npm explain minimatch` + - `npm audit --json` + - `npm audit --omit=dev --json` +- **status**: Blocked — review prepared, awaiting compatible fix or maintainer decision +- **log**: Concrete review packet committed in `713b3025`; no package mutation, approval or disposition change. Current development-tool advisory ranges and dependency parents checked; production web audit zero. See task evidence and `quality/reviews/exception-ownership.json`. +- **files edited/created**: + +### T7.10: Remediate deepmerge dependency advisory GHSA-gcqx-w48m-w2h4 +- **depends_on**: [T7, T8, T7.9] +- **agent_type**: local +- **ownership**: `web/package.json`, `web/package-lock.json`, `quality/evidence/T7.10.md`. +- **priority**: medium +- **description**: Resolve GHSA-gcqx-w48m-w2h4 for deepmerge using an actually available compatible patched transitive version or remove the vulnerable dependency path. Inspect npm explain and production/dev reachability. Preserve Node 24 and installed direct major versions. If no compatible fix exists, submit a specific owner/expiry/rationale proposal and keep this task open until explicitly reviewed; do not force an unrelated major upgrade. +- **findings**: DEPENDABOT-101, NPM-WEB-GHSA-gcqx-w48m-w2h4-deepmerge +- **acceptance_criteria**: + - npm audit no longer reports the advisory on each original dependency path, or an explicitly reviewed current exception documents the residual exposure. + - Dependency graph, unit tests and both web builds remain compatible; no broad overrides that violate parent ranges. +- **validation**: + - `npm --prefix web explain deepmerge` + - `npm --prefix web audit --json` + - `npm --prefix web audit --omit=dev --json` + - `npm --prefix web test -- --runInBand` + - `npm --prefix web run build` + - `LIGHTNVR_WEB_LEGACY=false npm --prefix web run build` +- **status**: Blocked — review prepared, awaiting compatible fix or maintainer decision +- **log**: Concrete review packet committed in `713b3025`; no package mutation, approval or disposition change. Current development-tool advisory ranges and dependency parents checked; production web audit zero. See task evidence and `quality/reviews/exception-ownership.json`. +- **files edited/created**: + +### T7.11: cpp/cleartext-storage-database in db_event_destinations.c +- **depends_on**: [T7, T13, T7.11.1] +- **agent_type**: worker +- **ownership**: `src/database/db_event_destinations.c`, `tests/unit/test_t7_11.c`, `tests/unit/cmake/eng/t7_11.cmake`, `quality/evidence/T7.11.md`. +- **priority**: high +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CODEQL-316, CODEQL-307 +- **acceptance_criteria**: + - Trace credential persistence and read access; document existing at-rest security and migration constraints. Test redaction and restricted access. If encryption changes the storage contract, materialize a separate bounded migration task instead of inventing a key-management scheme. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.11 --regex '^test_t7_11$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.11`; inspect the raw T1 report and trace the caller. +- **status**: Blocked — credential storage contract requires design and implementation or reviewed exception +- **log**: Both raw create/update sinks reproduced as plaintext in SQLite;8private DB/API controls pass for existing authorization/redaction, which do not remediate at-rest exposure. No production change or approval. T7.11.1 produces a concrete proposed contract before implementation can be scoped. +- **files edited/created**: + +### T7.12: cpp/cleartext-storage-database in db_streams.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/database/db_streams.c`, `tests/unit/test_t7_12.c`, `tests/unit/cmake/eng/t7_12.cmake`, `quality/evidence/T7.12.md`. +- **priority**: high +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CODEQL-185, CODEQL-183, CODEQL-181 +- **acceptance_criteria**: + - Trace credential persistence and read access; document existing at-rest security and migration constraints. Test redaction and restricted access. If encryption changes the storage contract, materialize a separate bounded migration task instead of inventing a key-management scheme. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.12 --regex '^test_t7_12$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.12`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.13: cpp/cleartext-storage-file in config.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/core/config.c`, `tests/unit/test_t7_13.c`, `tests/unit/cmake/eng/t7_13.cmake`, `quality/evidence/T7.13.md`. +- **priority**: high +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CODEQL-267, CODEQL-64, CODEQL-63 +- **acceptance_criteria**: + - Verify file permissions, creation umask, redacted logs and credential read access; preserve compatible config loading. Residual necessary plaintext needs explicit reviewed exposure acceptance. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.13 --regex '^test_t7_13$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.13`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.14: cpp/cleartext-transmission in hls_unified_thread.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/video/hls/hls_unified_thread.c`, `tests/unit/test_t7_14.c`, `tests/unit/cmake/eng/t7_14.cmake`, `quality/evidence/T7.14.md`. +- **priority**: high +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CODEQL-264 +- **acceptance_criteria**: + - Assert credential-bearing payloads are not disclosed to an untrusted/insecure destination; verify supported camera transport behavior. Any required legacy cleartext transport needs an explicit reviewed exposure exception. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.14 --regex '^test_t7_14$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.14`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.15: cpp/cleartext-transmission in stream_protocol.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/video/stream_protocol.c`, `tests/unit/test_t7_15.c`, `tests/unit/cmake/eng/t7_15.cmake`, `quality/evidence/T7.15.md`. +- **priority**: high +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CODEQL-14 +- **acceptance_criteria**: + - Assert credential-bearing payloads are not disclosed to an untrusted/insecure destination; verify supported camera transport behavior. Any required legacy cleartext transport needs an explicit reviewed exposure exception. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.15 --regex '^test_t7_15$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.15`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.16: cpp/integer-multiplication-cast-to-long in mp4_writer_utils.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/video/mp4_writer_utils.c`, `tests/unit/test_t7_16.c`, `tests/unit/cmake/eng/t7_16.cmake`, `quality/evidence/T7.16.md`. +- **priority**: high +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CODEQL-329 +- **acceptance_criteria**: + - Exercise signed/unsigned boundaries, negative/zero values, maximum accepted values and multiplication overflow before conversion; assert valid results are unchanged and invalid values cannot wrap, divide by zero or allocate oversized memory. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.16 --regex '^test_t7_16$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.16`; inspect the raw T1 report and trace the caller. +- **status**: Completed (commit `60bc1b68`) +- **log**: Widen AAC bitrate operand before multiplication;16tests (3expected unsigned-wrap RED cases) nowGREEN, plus related media controls36/36 ASan/UBSan. Huge-channel cases use explicit seams; actual FAAC support/invalid-input controls remain unchanged. Fresh CodeQL remains T33. +- **files edited/created**: + +### T7.17: cpp/non-https-url in go2rtc_api.c +- **depends_on**: [T7, T13, T7.2.7] +- **agent_type**: worker +- **ownership**: `src/video/go2rtc/go2rtc_api.c`, `tests/unit/test_t7_17.c`, `tests/unit/cmake/eng/t7_17.cmake`, `quality/evidence/T7.17.md`. +- **priority**: high +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CODEQL-78, CODEQL-77, CODEQL-76, CODEQL-75, CODEQL-71, CODEQL-70, CODEQL-69, CODEQL-20, CODEQL-19 +- **acceptance_criteria**: + - Prove endpoint trust and transport policy for each reported URL; retain HTTP-only camera compatibility only with an explicitly reviewed exposure exception. Do not rewrite device endpoints to HTTPS without negotiation/support. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.17 --regex '^test_t7_17$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.17`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.18: cpp/non-https-url in onvif_detection.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/video/onvif_detection.c`, `tests/unit/test_t7_18.c`, `tests/unit/cmake/eng/t7_18.cmake`, `quality/evidence/T7.18.md`. +- **priority**: high +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CODEQL-337, CODEQL-336, CODEQL-335, CODEQL-334, CODEQL-272, CODEQL-271, CODEQL-270 +- **acceptance_criteria**: + - Prove endpoint trust and transport policy for each reported URL; retain HTTP-only camera compatibility only with an explicitly reviewed exposure exception. Do not rewrite device endpoints to HTTPS without negotiation/support. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.18 --regex '^test_t7_18$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.18`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.19: cpp/non-https-url in onvif_discovery.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/video/onvif_discovery.c`, `tests/unit/test_t7_19.c`, `tests/unit/cmake/eng/t7_19.cmake`, `quality/evidence/T7.19.md`. +- **priority**: high +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CODEQL-59 +- **acceptance_criteria**: + - Prove endpoint trust and transport policy for each reported URL; retain HTTP-only camera compatibility only with an explicitly reviewed exposure exception. Do not rewrite device endpoints to HTTPS without negotiation/support. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.19 --regex '^test_t7_19$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.19`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.20: cpp/non-https-url in api_handlers_ptz.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_ptz.c`, `tests/unit/test_t7_20.c`, `tests/unit/cmake/eng/t7_20.cmake`, `quality/evidence/T7.20.md`. +- **priority**: high +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CODEQL-73 +- **acceptance_criteria**: + - Prove endpoint trust and transport policy for each reported URL; retain HTTP-only camera compatibility only with an explicitly reviewed exposure exception. Do not rewrite device endpoints to HTTPS without negotiation/support. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.20 --regex '^test_t7_20$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.20`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.21: cpp/path-injection in storage_s3.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/storage/storage_s3.c`, `tests/unit/test_t7_21.c`, `tests/unit/cmake/eng/t7_21.cmake`, `quality/evidence/T7.21.md`. +- **priority**: high +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CODEQL-325 +- **acceptance_criteria**: + - Reject dot segments, separators and encoded traversal from untrusted names before filesystem access; assert normal names still select the intended file and no outside-tree path is opened. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.21 --regex '^test_t7_21$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.21`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.22: cpp/path-injection in linux_cgroup.c, linux_network.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/telemetry/collectors/linux_cgroup.c`, `src/telemetry/collectors/linux_network.c`, `tests/unit/test_t7_22.c`, `tests/unit/cmake/eng/t7_22.cmake`, `quality/evidence/T7.22.md`. +- **priority**: high +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CODEQL-320, CODEQL-319, CODEQL-318, CODEQL-317 +- **acceptance_criteria**: + - Reject dot segments, separators and encoded traversal from untrusted names before filesystem access; assert normal names still select the intended file and no outside-tree path is opened. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.22 --regex '^test_t7_22$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.22`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.23: cpp/sql-injection in sqlite_migrate.c +- **depends_on**: [T7, T13, T7.23.3] +- **agent_type**: worker +- **ownership**: `src/database/sqlite_migrate.c`, `tests/unit/test_t7_23.c`, `tests/unit/cmake/eng/t7_23.cmake`, `quality/evidence/T7.23.md`. +- **priority**: high +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CODEQL-67, CODEQL-66 +- **acceptance_criteria**: + - Use quote, semicolon and comment-bearing identifiers in a disposable database; ensure they cannot execute a second statement or alter unrelated schema, while valid migration names still work. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.23 --regex '^test_t7_23$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.23`; inspect the raw T1 report and trace the caller. +- **status**: Completed (fixes `23e65c8b`, `9f65a26c`, `c98876b5`) +- **log**: Quoted/comment lexical controls, tracking-name escaping and trigger terminal-token review all verified. Expanded19focused+7policy+9storage+2initialization=37/37ASan/UBSan pass, all original failures retained. Full scanner/profile reconciliation remains T33. +- **files edited/created**: + +### T7.24: cpp/suspicious-pointer-scaling in frame.c +- **depends_on**: [T7, T13, T7.24.1, T7.24.3] +- **agent_type**: local +- **ownership**: `quality/reviews/t7_24-upstream.json`, `quality/evidence/T7.24.md`. +- **priority**: high +- **description**: Review pinned upstream source and reproduce the listed diagnostics in the actual linked feature profile. Record precise upstream fix/version or evidence-backed false-positive analysis. Do not edit system headers or vendored/submodule source as an untracked patch. If an update/local patch is needed, append an exact bounded integration child, dependency and regression command to T33 before closing this task. Missing upstream or feature evidence blocks completion. +- **findings**: CODEQL-330 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 -m json.tool quality/reviews/t7_24-upstream.json` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.24`; inspect the raw T1 report and trace the caller. +- **status**: Completed (tracked correction `9d845904`; actual CodeQL comparison recorded) +- **log**: Actual CLI2.27.3/cpp-queries1.9.2 comparison reproduces CODEQL-330 at frame.c:557 on20pristine upstream units and reports zero results for the same two pointer-scaling queries on20patched units. Both actual archives/source hashes verified; stable checkpoint4b6220f9. Prior36codec cases and43pipeline tests retained; full-suite/profile/final-revision closure remains T33. +- **files edited/created**: + +### T7.25: cpp/toctou-race-condition in db_core.c +- **depends_on**: [T7, T13, T7.25.1] +- **agent_type**: worker +- **ownership**: `src/database/db_core.c`, `tests/unit/test_t7_25.c`, `tests/unit/cmake/eng/t7_25.cmake`, `quality/evidence/T7.25.md`. +- **priority**: high +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CODEQL-321 +- **acceptance_criteria**: + - Exercise replacement/symlink races between validation and use; operations must hold and use the validated descriptor or reject changed identity, preserving normal success and cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.25 --regex '^test_t7_25$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.25`; inspect the raw T1 report and trace the caller. +- **status**: Completed — mapped stale-artifact race fixed (`54d2f7ca`) +- **log**: 153099d1 genuine3RED producer retained; identity-preserving stale-artifact helper reaches45/45relatedGREEN. ExactCODEQL-321 cause fixed without broadstorage policy changes; freshscanner reconciliation remainsT33. Separate published-backup pruning concern fenced asT7.25.2, not silently covered. +- **files edited/created**: + +### T7.26: cpp/toctou-race-condition in storage_migration.c +- **depends_on**: [T7, T13, T7.26.3] +- **agent_type**: worker +- **ownership**: `src/storage/storage_migration.c`, `tests/unit/test_t7_26.c`, `tests/unit/cmake/eng/t7_26.cmake`, `quality/evidence/T7.26.md`. +- **priority**: high +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CODEQL-310 +- **acceptance_criteria**: + - Exercise replacement/symlink races between validation and use; operations must hold and use the validated descriptor or reject changed identity, preserving normal success and cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.26 --regex '^test_t7_26$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.26`; inspect the raw T1 report and trace the caller. +- **status**: Completed — mapped publication race and confirmed ownership children fixed +- **log**: Producer dc571048 preserved; ordered fixes87b812ad/e3321d58/d829c053/eeeb1c5e resolve final overwrite, unowned cleanup, preexisting temporary and verified substitution causes. Final37/37 native cases pass. No universal concurrency guarantee or fresh scanner dismissal; explicit limits and T33 reconciliation remain. +- **files edited/created**: + +### T7.27: cpp/toctou-race-condition in api_handlers_operator_floor_plans.c +- **depends_on**: [T7, T13, T7.27.3] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_operator_floor_plans.c`, `tests/unit/test_t7_27.c`, `tests/unit/cmake/eng/t7_27.cmake`, `quality/evidence/T7.27.md`. +- **priority**: high +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CODEQL-315, CODEQL-314, CODEQL-313 +- **acceptance_criteria**: + - Exercise replacement/symlink races between validation and use; operations must hold and use the validated descriptor or reject changed identity, preserving normal success and cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.27 --regex '^test_t7_27$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.27`; inspect the raw T1 report and trace the caller. +- **status**: Completed (fixes `dc287459`, `a8ce89a3`, `44dbb7ee`) +- **log**: All three scoped filesystem identity/publication children complete; final 24 focused plus 8 existing cases pass under ASan/UBSan. Unknown artifacts and publication/rollback competitors retained; filesystem/SQLite window and other documented limits remain explicit. Fresh full scanner reconciliation remains T33. +- **files edited/created**: + +### T7.28: cpp/uncontrolled-allocation-size in event_router.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/core/event_router.c`, `tests/unit/test_t7_28.c`, `tests/unit/cmake/eng/t7_28.cmake`, `quality/evidence/T7.28.md`. +- **priority**: high +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CODEQL-306, CODEQL-305 +- **acceptance_criteria**: + - Exercise zero, maximum accepted, one-past maximum and overflow-sized user inputs; reject before allocation/multiplication, leave state unchanged, and preserve ordinary delivery. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.28 --regex '^test_t7_28$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.28`; inspect the raw T1 report and trace the caller. +- **status**: Pending review — existing allocation guards verified; no approval recorded +- **log**: Tests-only46812624:19new and7existing router cases pass ASan/UBSan. Original/current TZif bounds already limit both reported allocations; proposed already-guarded disposition requires review and freshT33scan. No production, baseline or exception change. +- **files edited/created**: + +### T7.29: cpp/world-writable-file-creation in mqtt_client.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/core/mqtt_client.c`, `tests/unit/test_t7_29.c`, `tests/unit/cmake/eng/t7_29.cmake`, `quality/evidence/T7.29.md`. +- **priority**: high +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CODEQL-293 +- **acceptance_criteria**: + - Create the credential-bearing file under a permissive umask and assert effective permissions exclude group/other access; retain existing owner and normal reload semantics. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.29 --regex '^test_t7_29$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.29`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.30: arrayIndexOutOfBounds in stream_protocol.c +- **depends_on**: [T7, T13, T7.15] +- **agent_type**: worker +- **ownership**: `src/video/stream_protocol.c`, `tests/unit/test_t7_30.c`, `tests/unit/cmake/eng/t7_30.cmake`, `quality/evidence/T7.30.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-0dfdce44e3f2c522 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.30 --regex '^test_t7_30$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.30`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.31: clang-analyzer-core.CallAndMessage in system_health_policy.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/telemetry/system_health_policy.c`, `tests/unit/test_t7_31.c`, `tests/unit/cmake/eng/t7_31.cmake`, `quality/evidence/T7.31.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-9b21a14b97b3a15e +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.31 --regex '^test_t7_31$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.31`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.32: clang-analyzer-core.DivideZero in db_storage_pools.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/database/db_storage_pools.c`, `tests/unit/test_t7_32.c`, `tests/unit/cmake/eng/t7_32.cmake`, `quality/evidence/T7.32.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-b7cb62f9287bca53 +- **acceptance_criteria**: + - Exercise signed/unsigned boundaries, negative/zero values, maximum accepted values and multiplication overflow before conversion; assert valid results are unchanged and invalid values cannot wrap, divide by zero or allocate oversized memory. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.32 --regex '^test_t7_32$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.32`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.33: clang-analyzer-core.NonNullParamChecker in camera_collection_filter.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/core/camera_collection_filter.c`, `tests/unit/test_t7_33.c`, `tests/unit/cmake/eng/t7_33.cmake`, `quality/evidence/T7.33.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-a918c6008bfe9234 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.33 --regex '^test_t7_33$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.33`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.34: clang-analyzer-core.NonNullParamChecker in db_storage_policies.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/database/db_storage_policies.c`, `tests/unit/test_t7_34.c`, `tests/unit/cmake/eng/t7_34.cmake`, `quality/evidence/T7.34.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-a5ab5fe6add78d8e +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.34 --regex '^test_t7_34$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.34`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.35: clang-analyzer-core.NonNullParamChecker in storage_s3.c +- **depends_on**: [T7, T13, T7.21] +- **agent_type**: worker +- **ownership**: `src/storage/storage_s3.c`, `tests/unit/test_t7_35.c`, `tests/unit/cmake/eng/t7_35.cmake`, `quality/evidence/T7.35.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-cb260984bd5750f8 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.35 --regex '^test_t7_35$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.35`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.36: clang-analyzer-core.NullDereference in event_envelope.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/core/event_envelope.c`, `tests/unit/test_t7_36.c`, `tests/unit/cmake/eng/t7_36.cmake`, `quality/evidence/T7.36.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-f240093f03b567fb +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.36 --regex '^test_t7_36$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.36`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.37: clang-analyzer-core.NullDereference in db_live_saved_layouts.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/database/db_live_saved_layouts.c`, `tests/unit/test_t7_37.c`, `tests/unit/cmake/eng/t7_37.cmake`, `quality/evidence/T7.37.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-f8203edcc8863cd8 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.37 --regex '^test_t7_37$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.37`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.38: clang-analyzer-core.NullDereference in db_storage_policies.c +- **depends_on**: [T7, T13, T7.34] +- **agent_type**: worker +- **ownership**: `src/database/db_storage_policies.c`, `tests/unit/test_t7_38.c`, `tests/unit/cmake/eng/t7_38.cmake`, `quality/evidence/T7.38.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-179513c629f1ecac +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.38 --regex '^test_t7_38$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.38`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.39: clang-analyzer-core.NullDereference in smartctl_provider.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/telemetry/providers/smartctl_provider.c`, `tests/unit/test_t7_39.c`, `tests/unit/cmake/eng/t7_39.cmake`, `quality/evidence/T7.39.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-33d1c32daba1778e +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.39 --regex '^test_t7_39$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.39`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.40: clang-analyzer-core.NullDereference in go2rtc_api.c +- **depends_on**: [T7, T13, T7.17] +- **agent_type**: worker +- **ownership**: `src/video/go2rtc/go2rtc_api.c`, `tests/unit/test_t7_40.c`, `tests/unit/cmake/eng/t7_40.cmake`, `quality/evidence/T7.40.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-14fc6cb721e4ff46 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.40 --regex '^test_t7_40$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.40`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.41: clang-analyzer-core.NullDereference in hls_writer.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/video/hls_writer.c`, `tests/unit/test_t7_41.c`, `tests/unit/cmake/eng/t7_41.cmake`, `quality/evidence/T7.41.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-848024a1059f9105, CLANGTIDY-c18dbfa67bfd519e +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.41 --regex '^test_t7_41$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.41`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.42: clang-analyzer-core.NullDereference in api_handlers_authorization.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_authorization.c`, `tests/unit/test_t7_42.c`, `tests/unit/cmake/eng/t7_42.cmake`, `quality/evidence/T7.42.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-3853c804fd823d76, CLANGTIDY-e903e0b58c1ab7fb, CLANGTIDY-7037bcf50d8dd6e1, CLANGTIDY-468c829b1cc867f5, CLANGTIDY-5546a3c4710ffc07, CLANGTIDY-9c42b9e1d1f45117, CLANGTIDY-b45db718e3e94b0d +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.42 --regex '^test_t7_42$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.42`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.43: clang-analyzer-core.NullDereference in api_handlers_fleet.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_fleet.c`, `tests/unit/test_t7_43.c`, `tests/unit/cmake/eng/t7_43.cmake`, `quality/evidence/T7.43.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-dc1d8989b6dc0f0a +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.43 --regex '^test_t7_43$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.43`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.44: clang-analyzer-core.NullDereference in api_handlers_health.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_health.c`, `tests/unit/test_t7_44.c`, `tests/unit/cmake/eng/t7_44.cmake`, `quality/evidence/T7.44.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-18224b07c8f96008 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.44 --regex '^test_t7_44$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.44`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.45: clang-analyzer-core.NullDereference in api_handlers_lpr.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_lpr.c`, `tests/unit/test_t7_45.c`, `tests/unit/cmake/eng/t7_45.cmake`, `quality/evidence/T7.45.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-36ab8111fbec813b, CLANGTIDY-21f4fa94b0f2d17d +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.45 --regex '^test_t7_45$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.45`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.46: clang-analyzer-core.NullDereference in api_handlers_recording_control.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_recording_control.c`, `tests/unit/test_t7_46.c`, `tests/unit/cmake/eng/t7_46.cmake`, `quality/evidence/T7.46.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-e722e2369c13444f +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.46 --regex '^test_t7_46$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.46`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.47: clang-analyzer-core.NullDereference in api_handlers_retention.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_retention.c`, `tests/unit/test_t7_47.c`, `tests/unit/cmake/eng/t7_47.cmake`, `quality/evidence/T7.47.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-67d853afc9b7eefe +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.47 --regex '^test_t7_47$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.47`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.48: clang-analyzer-core.NullDereference in api_handlers_storage_migrations.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_storage_migrations.c`, `tests/unit/test_t7_48.c`, `tests/unit/cmake/eng/t7_48.cmake`, `quality/evidence/T7.48.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-44c26e7cc4f14d25, CLANGTIDY-7f68b57abf95cdae +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.48 --regex '^test_t7_48$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.48`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.49: clang-analyzer-core.NullDereference in api_handlers_streams_get.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_streams_get.c`, `tests/unit/test_t7_49.c`, `tests/unit/cmake/eng/t7_49.cmake`, `quality/evidence/T7.49.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-2b9b559954a51bc7 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.49 --regex '^test_t7_49$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.49`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.50: clang-analyzer-core.NullDereference in api_handlers_streams_modify.c +- **depends_on**: [T7, T13, T13.3, T13.4] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_streams_modify.c`, `tests/unit/test_t7_50.c`, `tests/unit/cmake/eng/t7_50.cmake`, `quality/evidence/T7.50.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-3ac69a72123df761 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.50 --regex '^test_t7_50$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.50`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.51: clang-analyzer-core.NullDereference in api_handlers_system_health.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_system_health.c`, `tests/unit/test_t7_51.c`, `tests/unit/cmake/eng/t7_51.cmake`, `quality/evidence/T7.51.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-2eb8f8220e64672b, CLANGTIDY-8935718b76906f1e, CLANGTIDY-db6d4840afaa43ce, CLANGTIDY-6253a319b7ed3b31 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.51 --regex '^test_t7_51$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.51`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.52: clang-analyzer-core.UndefinedBinaryOperatorResult in path_utils.c +- **depends_on**: [T7, T13, T7.2.8] +- **agent_type**: worker +- **ownership**: `src/core/path_utils.c`, `tests/unit/test_t7_52.c`, `tests/unit/cmake/eng/t7_52.cmake`, `quality/evidence/T7.52.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-d86514e7015dbd70 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.2.8 --profile integration --regex '^test_(t7_2_files|path_utils|storage_(deletion_ledger|manager_retention)|stale_recording_prune|hls_cache_cleanup)$' --jobs 4` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.52`; inspect the raw T1 report and trace the caller. +- **status**: Completed — exact existing RED/GREEN regression reused +- **log**: Raw T1 path_utils.c:224 garbage-fd diagnostic follows the exact ELOOP branch fixed by e3ec7ea6. Retained T7.2.3 descriptor regression741beec3 reproduces closure of privatefd0 and passes after initialization;110related cases pass. No redundant test or fresh scanner-clean claim. +- **files edited/created**: + +### T7.53: clang-analyzer-security.ArrayBound in authorization.c +- **depends_on**: [T7, T13, T7.53.1] +- **agent_type**: worker +- **ownership**: `src/core/authorization.c`, `tests/unit/test_t7_53.c`, `tests/unit/cmake/eng/t7_53.cmake`, `quality/evidence/T7.53.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-c4536c7eb269c97d, CLANGTIDY-dc57edf3e0b50ae4 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.53 --regex '^test_t7_53$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.53`; inspect the raw T1 report and trace the caller. +- **status**: Partial — arithmetic fix verified; raw diagnostic disposition pending +- **log**: d9f30f1e guards signed capacity doubling and size multiplication after2genuine seam-only RED cases. Full44authorization/cache tests now pass after separate fixture repair230c6767. Opaque-context negative-index scanner path was not reproduced through public inputs; no approved false-positive or fresh scanner absence claimed. +- **files edited/created**: + +### T7.54: clang-analyzer-security.ArrayBound in logger_json.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/core/logger_json.c`, `tests/unit/test_t7_54.c`, `tests/unit/cmake/eng/t7_54.cmake`, `quality/evidence/T7.54.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-ff30996a0be1b3ef +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.54 --regex '^test_t7_54$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.54`; inspect the raw T1 report and trace the caller. +- **status**: Pending review — existing log-read bounds verified; no approval recorded +- **log**: Tests-only0f11ff1d:17new plus41existing logger cases pass ASan/UBSan. Original/current allocation and clamp already bound terminator; no production change or approved false-positive. Fresh scanner review remainsT33. +- **files edited/created**: + +### T7.55: clang-analyzer-security.ArrayBound in main.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/core/main.c`, `tests/unit/test_t7_55.c`, `tests/unit/cmake/eng/t7_55.cmake`, `quality/evidence/T7.55.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-470e897ffa76864e +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.55 --regex '^test_t7_55$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.55`; inspect the raw T1 report and trace the caller. +- **status**: Pending review — existing process-name bound verified; no approval recorded +- **log**: Unchanged actual classifier has fgets32/NUL bound before strcspn write. Twelve actual-helper boundary/I-O cases pass ASan/UBSan initially. Fresh scoped clang21 still emits mapped diagnostic; no source change, fabricated RED, baseline update or dismissal. Evidence T7.55.md. +- **files edited/created**: + +### T7.56: clang-analyzer-security.ArrayBound in sqlite_migrate.c +- **depends_on**: [T7, T13, T7.23] +- **agent_type**: worker +- **ownership**: `src/database/sqlite_migrate.c`, `tests/unit/test_t7_56.c`, `tests/unit/cmake/eng/t7_56.cmake`, `quality/evidence/T7.56.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-2772ec37ee463512 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.56 --regex '^test_t7_56$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.56`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.57: clang-analyzer-security.ArrayBound in linux_network.c, linux_process.c, linux_restart.c, linux_thermal.c +- **depends_on**: [T7, T13, T7.22] +- **agent_type**: worker +- **ownership**: `src/telemetry/collectors/linux_network.c`, `src/telemetry/collectors/linux_process.c`, `src/telemetry/collectors/linux_restart.c`, `src/telemetry/collectors/linux_thermal.c`, `tests/unit/test_t7_57.c`, `tests/unit/cmake/eng/t7_57.cmake`, `quality/evidence/T7.57.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-9259d0adb44e3ab3, CLANGTIDY-55f597a830e65b4d, CLANGTIDY-eaf20c1f0de62870, CLANGTIDY-c05a923060c50809, CLANGTIDY-30cd36651cb697e6 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.57 --regex '^test_t7_57$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.57`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.58: clang-analyzer-security.ArrayBound in linux_hardware.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/telemetry/providers/linux_hardware.c`, `tests/unit/test_t7_58.c`, `tests/unit/cmake/eng/t7_58.cmake`, `quality/evidence/T7.58.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-29fe00c504db3f7e, CLANGTIDY-93f7df7451e39223 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.58 --regex '^test_t7_58$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.58`; inspect the raw T1 report and trace the caller. +- **status**: Pending review — existing hardware bounds verified; no approval recorded +- **log**: Twelve actual-helper boundary cases plus seven existing hardware cases pass ASan/UBSan initially. Both raw diagnostics persist in fresh scoped clang21 output; source bounds and original helper equivalence documented. No production change, fabricated RED, finding dismissal or baseline approval. +- **files edited/created**: + +### T7.59: clang-analyzer-security.ArrayBound in system_health.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/telemetry/system_health.c`, `tests/unit/test_t7_59.c`, `tests/unit/cmake/eng/t7_59.cmake`, `quality/evidence/T7.59.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-192595ad5f7437f2 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.59 --regex '^test_t7_59$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.59`; inspect the raw T1 report and trace the caller. +- **status**: Pending review (characterization `58ebf811`) +- **log**: Seven actual worker-boundary cases plus four type cases pass initially; constructed tiers are 0..3 and public invalid tiers are rejected. Fresh clang21 still reports the mapped array warning. No production correction, false-positive approval or baseline disposition claimed. Separate startup deadlock producer/correction tracked as T7.59.1/.2. +- **files edited/created**: + +### T7.60: clang-analyzer-security.ArrayBound in go2rtc_process.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/video/go2rtc/go2rtc_process.c`, `tests/unit/test_t7_60.c`, `tests/unit/cmake/eng/t7_60.cmake`, `quality/evidence/T7.60.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-6eb7be7d5d4be88a +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.60 --regex '^test_t7_60$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.60`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.61: clang-analyzer-security.ArrayBound in hls_cache_cleanup.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/video/hls/hls_cache_cleanup.c`, `tests/unit/test_t7_61.c`, `tests/unit/cmake/eng/t7_61.cmake`, `quality/evidence/T7.61.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-e88c078f85b44a5e +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.61 --regex '^test_t7_61$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.61`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.62: clang-analyzer-security.ArrayBound in stream_manager.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/video/stream_manager.c`, `tests/unit/test_t7_62.c`, `tests/unit/cmake/eng/t7_62.cmake`, `quality/evidence/T7.62.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-87fe5c4f9b2c4103 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.62 --regex '^test_t7_62$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.62`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.63: clang-analyzer-security.ArrayBound in api_handlers_fleet.c +- **depends_on**: [T7, T13, T7.43] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_fleet.c`, `tests/unit/test_t7_63.c`, `tests/unit/cmake/eng/t7_63.cmake`, `quality/evidence/T7.63.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-b6500f12e4ee30b7, CLANGTIDY-a01b119b5b6a4463 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.63 --regex '^test_t7_63$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.63`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.64: clang-analyzer-security.ArrayBound in api_handlers_streams_get.c +- **depends_on**: [T7, T13, T7.49] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_streams_get.c`, `tests/unit/test_t7_64.c`, `tests/unit/cmake/eng/t7_64.cmake`, `quality/evidence/T7.64.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-d562d8dbfbb1dcc3, CLANGTIDY-24b2873ab4fb113e +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.64 --regex '^test_t7_64$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.64`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.65: clang-analyzer-security.ArrayBound in api_handlers_system.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_system.c`, `tests/unit/test_t7_65.c`, `tests/unit/cmake/eng/t7_65.cmake`, `quality/evidence/T7.65.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-4f6abbd42f9a1855, CLANGTIDY-c7df8f3cce68c5a4 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.65 --regex '^test_t7_65$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.65`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.66: clang-analyzer-security.insecureAPI.strcpy in recording_transcode.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/video/recording_transcode.c`, `tests/unit/test_t7_66.c`, `tests/unit/cmake/eng/t7_66.cmake`, `quality/evidence/T7.66.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-d6eef8565b934dd8, CLANGTIDY-4976fee144691228 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.66 --regex '^test_t7_66$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.66`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.67: clang-analyzer-security.insecureAPI.strcpy in api_handlers_go2rtc_proxy.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_go2rtc_proxy.c`, `tests/unit/test_t7_67.c`, `tests/unit/cmake/eng/t7_67.cmake`, `quality/evidence/T7.67.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-4c710ebaa0f3ef18, CLANGTIDY-e7f2b500346b1db4, CLANGTIDY-59d7e244d1294daa, CLANGTIDY-de5c9750f9e973f2 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.67 --regex '^test_t7_67$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.67`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.68: clang-analyzer-unix.BlockInCriticalSection in health_helper_runner.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/telemetry/health_helper_runner.c`, `tests/unit/test_t7_68.c`, `tests/unit/cmake/eng/t7_68.cmake`, `quality/evidence/T7.68.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-8a0289975bb00ddb +- **acceptance_criteria**: + - Exercise the blocking operation and concurrent shutdown/contending thread with bounded deadlines; assert no lock is held across unbounded I/O and lock-protected invariants remain intact. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.68 --regex '^test_t7_68$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.68`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.69: clang-analyzer-unix.BlockInCriticalSection in hls_unified_thread.c +- **depends_on**: [T7, T13, T7.14] +- **agent_type**: worker +- **ownership**: `src/video/hls/hls_unified_thread.c`, `tests/unit/test_t7_69.c`, `tests/unit/cmake/eng/t7_69.cmake`, `quality/evidence/T7.69.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-1728287dd1cfa5a4 +- **acceptance_criteria**: + - Exercise the blocking operation and concurrent shutdown/contending thread with bounded deadlines; assert no lock is held across unbounded I/O and lock-protected invariants remain intact. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.69 --regex '^test_t7_69$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.69`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.70: clang-analyzer-unix.Errno in storage_migration.c +- **depends_on**: [T7, T13, T7.26] +- **agent_type**: worker +- **ownership**: `src/storage/storage_migration.c`, `tests/unit/test_t7_70.c`, `tests/unit/cmake/eng/t7_70.cmake`, `quality/evidence/T7.70.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-f15ef4cfb6675401, CLANGTIDY-d8903761e409b32d +- **acceptance_criteria**: + - Inject failing and successful calls with stale errno; branch only on actual call failure and preserve the original error before cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.70 --regex '^test_t7_70$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.70`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.71: clang-analyzer-unix.StdCLibraryFunctions in hls_cache_cleanup.c +- **depends_on**: [T7, T13, T7.61] +- **agent_type**: worker +- **ownership**: `src/video/hls/hls_cache_cleanup.c`, `tests/unit/test_t7_71.c`, `tests/unit/cmake/eng/t7_71.cmake`, `quality/evidence/T7.71.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-f70e12d3b12447f9, CLANGTIDY-81dd4bf2eb1bd4a8 +- **acceptance_criteria**: + - Exercise every reported format/library call with boundary values; argument types/counts, buffer extents and returned errors must match the library contract with no UB. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.71 --regex '^test_t7_71$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.71`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.72: clang-analyzer-unix.Stream in linux_network.c +- **depends_on**: [T7, T13, T7.57] +- **agent_type**: worker +- **ownership**: `src/telemetry/collectors/linux_network.c`, `tests/unit/test_t7_72.c`, `tests/unit/cmake/eng/t7_72.cmake`, `quality/evidence/T7.72.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-b38fd5e59ea07d0e, CLANGTIDY-ebeb3bd843bc6421 +- **acceptance_criteria**: + - Inject each allocation/open and downstream failure at the reported site; assert allocations, FILE handles and descriptors return to baseline on every exit and successful ownership transfers remain valid. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.72 --regex '^test_t7_72$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.72`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.73: integerOverflow in config.c +- **depends_on**: [T7, T13, T7.13] +- **agent_type**: worker +- **ownership**: `src/core/config.c`, `tests/unit/test_t7_73.c`, `tests/unit/cmake/eng/t7_73.cmake`, `quality/evidence/T7.73.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-a8a2fee09e04d7a2 +- **acceptance_criteria**: + - Exercise signed/unsigned boundaries, negative/zero values, maximum accepted values and multiplication overflow before conversion; assert valid results are unchanged and invalid values cannot wrap, divide by zero or allocate oversized memory. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.73 --regex '^test_t7_73$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.73`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.74: memleak in sod_img_writer.h +- **depends_on**: [T7, T13] +- **agent_type**: local +- **ownership**: `quality/reviews/t7_74-upstream.json`, `quality/evidence/T7.74.md`. +- **priority**: medium +- **description**: Review pinned upstream source and reproduce the listed diagnostics in the actual linked feature profile. Record precise upstream fix/version or evidence-backed false-positive analysis. Do not edit system headers or vendored/submodule source as an untracked patch. If an update/local patch is needed, append an exact bounded integration child, dependency and regression command to T33 before closing this task. Missing upstream or feature evidence blocks completion. +- **findings**: CPPCHECK-582ed0e8d5a10fc1 +- **acceptance_criteria**: + - Inject each allocation/open and downstream failure at the reported site; assert allocations, FILE handles and descriptors return to baseline on every exit and successful ownership transfers remain valid. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 -m json.tool quality/reviews/t7_74-upstream.json` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.74`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.75: memleak in sod.c +- **depends_on**: [T7, T13] +- **agent_type**: local +- **ownership**: `quality/reviews/t7_75-upstream.json`, `quality/evidence/T7.75.md`. +- **priority**: medium +- **description**: Review pinned upstream source and reproduce the listed diagnostics in the actual linked feature profile. Record precise upstream fix/version or evidence-backed false-positive analysis. Do not edit system headers or vendored/submodule source as an untracked patch. If an update/local patch is needed, append an exact bounded integration child, dependency and regression command to T33 before closing this task. Missing upstream or feature evidence blocks completion. +- **findings**: CPPCHECK-d14ab8cc9d321477, CPPCHECK-7bc58272fe78e01c, CPPCHECK-d1406d76ca075628, CPPCHECK-187d49bac01afe0b, CPPCHECK-01d35c4f8b07a420, CPPCHECK-7ed0f3ac85abfc0f, CPPCHECK-afd0bb6171ab9102, CPPCHECK-6c275f8832cf9762, CPPCHECK-340c5f9dec875049, CPPCHECK-94d7d3c523b01523, CPPCHECK-65f97e38c1e1c5df, CPPCHECK-377feb103d4c3b66 +- **acceptance_criteria**: + - Inject each allocation/open and downstream failure at the reported site; assert allocations, FILE handles and descriptors return to baseline on every exit and successful ownership transfers remain valid. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 -m json.tool quality/reviews/t7_75-upstream.json` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.75`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.76: preprocessorErrorDirective in nvme_provider.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/telemetry/providers/nvme_provider.c`, `tests/unit/test_t7_76.c`, `tests/unit/cmake/eng/t7_76.cmake`, `quality/evidence/T7.76.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-1c7b293c20ba8b3d +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.76 --regex '^test_t7_76$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.76`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.77: preprocessorErrorDirective in mp4_writer_utils.c +- **depends_on**: [T7, T13, T7.16] +- **agent_type**: worker +- **ownership**: `src/video/mp4_writer_utils.c`, `tests/unit/test_t7_77.c`, `tests/unit/cmake/eng/t7_77.cmake`, `quality/evidence/T7.77.md`. +- **priority**: medium +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-1afeb1315d85ff61 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.77 --regex '^test_t7_77$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.77`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.78: arrayIndexOutOfBoundsCond in stream_protocol.c +- **depends_on**: [T7, T13, T7.30] +- **agent_type**: worker +- **ownership**: `src/video/stream_protocol.c`, `tests/unit/test_t7_78.c`, `tests/unit/cmake/eng/t7_78.cmake`, `quality/evidence/T7.78.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-1d3bfb4e5fc184fe +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.78 --regex '^test_t7_78$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.78`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.79: bugprone-assignment-in-if-condition in db_system_health_incidents.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/database/db_system_health_incidents.c`, `tests/unit/test_t7_79.c`, `tests/unit/cmake/eng/t7_79.cmake`, `quality/evidence/T7.79.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-808f26556ac98110, CLANGTIDY-7e1ce15fe947a21d +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.79 --regex '^test_t7_79$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.79`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.80: bugprone-assignment-in-if-condition in api_detection.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/video/api_detection.c`, `tests/unit/test_t7_80.c`, `tests/unit/cmake/eng/t7_80.cmake`, `quality/evidence/T7.80.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-c34509f97f5eb3bf, CLANGTIDY-6ac00b1420332d40, CLANGTIDY-1848d1ca57216f44, CLANGTIDY-be52eb80a1a9b4e2 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.80 --regex '^test_t7_80$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.80`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.81: bugprone-assignment-in-if-condition in api_handlers_metrics.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_metrics.c`, `tests/unit/test_t7_81.c`, `tests/unit/cmake/eng/t7_81.cmake`, `quality/evidence/T7.81.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-02365646d53a2c50, CLANGTIDY-a219d74f760de10e, CLANGTIDY-96f8eef93ec11127, CLANGTIDY-221d97bd8b8ca157, CLANGTIDY-b6b7c0de32a0c6b7, CLANGTIDY-4edbb01e2c9075ed, CLANGTIDY-ec9cd5d6ab7a3d6e, CLANGTIDY-2f30db61a3888f56 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.81 --regex '^test_t7_81$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.81`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.82: bugprone-assignment-in-if-condition in go2rtc_websocket.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/web/go2rtc_websocket.c`, `tests/unit/test_t7_82.c`, `tests/unit/cmake/eng/t7_82.cmake`, `quality/evidence/T7.82.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-82315a3f137f4aff +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.82 --regex '^test_t7_82$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.82`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.83: bugprone-branch-clone in linux_cgroup.c +- **depends_on**: [T7, T13, T7.22] +- **agent_type**: worker +- **ownership**: `src/telemetry/collectors/linux_cgroup.c`, `tests/unit/test_t7_83.c`, `tests/unit/cmake/eng/t7_83.cmake`, `quality/evidence/T7.83.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-eac1097bd3780c16 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.83 --regex '^test_t7_83$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.83`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.84: bugprone-branch-clone in api_handlers_authorization.c +- **depends_on**: [T7, T13, T7.42] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_authorization.c`, `tests/unit/test_t7_84.c`, `tests/unit/cmake/eng/t7_84.cmake`, `quality/evidence/T7.84.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-a03bd79d6040e625 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.84 --regex '^test_t7_84$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.84`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.85: bugprone-branch-clone in api_handlers_timeline.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_timeline.c`, `tests/unit/test_t7_85.c`, `tests/unit/cmake/eng/t7_85.cmake`, `quality/evidence/T7.85.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-f1f70f55c95dfbaf +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.85 --regex '^test_t7_85$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.85`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.86: bugprone-branch-clone in audit_log.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/web/audit_log.c`, `tests/unit/test_t7_86.c`, `tests/unit/cmake/eng/t7_86.cmake`, `quality/evidence/T7.86.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-5bdf00371aedee4a +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.86 --regex '^test_t7_86$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.86`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.87: bugprone-branch-clone in thumbnail_thread.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/web/thumbnail_thread.c`, `tests/unit/test_t7_87.c`, `tests/unit/cmake/eng/t7_87.cmake`, `quality/evidence/T7.87.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-ccff9a5b097b862e +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.87 --regex '^test_t7_87$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.87`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.88: bugprone-implicit-widening-of-multiplication-result in event_bus.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/core/event_bus.c`, `tests/unit/test_t7_88.c`, `tests/unit/cmake/eng/t7_88.cmake`, `quality/evidence/T7.88.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-73322317a31f8bcc, CLANGTIDY-dbe8bf00e4b146ee +- **acceptance_criteria**: + - Exercise signed/unsigned boundaries, negative/zero values, maximum accepted values and multiplication overflow before conversion; assert valid results are unchanged and invalid values cannot wrap, divide by zero or allocate oversized memory. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.88 --regex '^test_t7_88$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.88`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.89: bugprone-implicit-widening-of-multiplication-result in event_envelope.c +- **depends_on**: [T7, T13, T7.36] +- **agent_type**: worker +- **ownership**: `src/core/event_envelope.c`, `tests/unit/test_t7_89.c`, `tests/unit/cmake/eng/t7_89.cmake`, `quality/evidence/T7.89.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-2fcad01867c4b146, CLANGTIDY-51ab98673b990b2c, CLANGTIDY-a48d8d980b8dbfcc +- **acceptance_criteria**: + - Exercise signed/unsigned boundaries, negative/zero values, maximum accepted values and multiplication overflow before conversion; assert valid results are unchanged and invalid values cannot wrap, divide by zero or allocate oversized memory. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.89 --regex '^test_t7_89$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.89`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.90: bugprone-implicit-widening-of-multiplication-result in event_router.c +- **depends_on**: [T7, T13, T7.28] +- **agent_type**: worker +- **ownership**: `src/core/event_router.c`, `tests/unit/test_t7_90.c`, `tests/unit/cmake/eng/t7_90.cmake`, `quality/evidence/T7.90.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-767515747827ef14 +- **acceptance_criteria**: + - Exercise signed/unsigned boundaries, negative/zero values, maximum accepted values and multiplication overflow before conversion; assert valid results are unchanged and invalid values cannot wrap, divide by zero or allocate oversized memory. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.90 --regex '^test_t7_90$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.90`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.91: bugprone-implicit-widening-of-multiplication-result in mqtt_delivery_worker.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/core/mqtt_delivery_worker.c`, `tests/unit/test_t7_91.c`, `tests/unit/cmake/eng/t7_91.cmake`, `quality/evidence/T7.91.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-ced78137a6871fda +- **acceptance_criteria**: + - Exercise signed/unsigned boundaries, negative/zero values, maximum accepted values and multiplication overflow before conversion; assert valid results are unchanged and invalid values cannot wrap, divide by zero or allocate oversized memory. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.91 --regex '^test_t7_91$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.91`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.92: bugprone-implicit-widening-of-multiplication-result in db_operator_floor_plans.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/database/db_operator_floor_plans.c`, `tests/unit/test_t7_92.c`, `tests/unit/cmake/eng/t7_92.cmake`, `quality/evidence/T7.92.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-4ce72e7a43d0bcce +- **acceptance_criteria**: + - Exercise signed/unsigned boundaries, negative/zero values, maximum accepted values and multiplication overflow before conversion; assert valid results are unchanged and invalid values cannot wrap, divide by zero or allocate oversized memory. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.92 --regex '^test_t7_92$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.92`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.93: bugprone-implicit-widening-of-multiplication-result in storage_migration.c +- **depends_on**: [T7, T13, T7.70] +- **agent_type**: worker +- **ownership**: `src/storage/storage_migration.c`, `tests/unit/test_t7_93.c`, `tests/unit/cmake/eng/t7_93.cmake`, `quality/evidence/T7.93.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-50703d3018f9bed5, CLANGTIDY-15fa53ad8b574808, CLANGTIDY-e5960a2d15356c27, CLANGTIDY-1fac122c6010eb36 +- **acceptance_criteria**: + - Exercise signed/unsigned boundaries, negative/zero values, maximum accepted values and multiplication overflow before conversion; assert valid results are unchanged and invalid values cannot wrap, divide by zero or allocate oversized memory. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.93 --regex '^test_t7_93$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.93`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.94: bugprone-implicit-widening-of-multiplication-result in storage_s3.c +- **depends_on**: [T7, T13, T7.35] +- **agent_type**: worker +- **ownership**: `src/storage/storage_s3.c`, `tests/unit/test_t7_94.c`, `tests/unit/cmake/eng/t7_94.cmake`, `quality/evidence/T7.94.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-612c846365824225, CLANGTIDY-1e0ea76108754cd5, CLANGTIDY-ab4dc4b3768799ca, CLANGTIDY-14f55668eaa243b0, CLANGTIDY-fbf040917d8b6cf7, CLANGTIDY-52dada64b4a6e625, CLANGTIDY-d1d1cf8dcb7070f8, CLANGTIDY-bfb7d2825452ce0c, CLANGTIDY-842c6bb171a64e75, CLANGTIDY-4a2a60d787933d7d, CLANGTIDY-a145bd05b0189193, CLANGTIDY-f72760acc6c09e16, CLANGTIDY-0866b8e57f533141, CLANGTIDY-d08422316656b39c, CLANGTIDY-d7c0ab1bf29a42a3 +- **acceptance_criteria**: + - Exercise signed/unsigned boundaries, negative/zero values, maximum accepted values and multiplication overflow before conversion; assert valid results are unchanged and invalid values cannot wrap, divide by zero or allocate oversized memory. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.94 --regex '^test_t7_94$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.94`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.95: bugprone-implicit-widening-of-multiplication-result in linux_network.c, linux_process.c, linux_thermal.c +- **depends_on**: [T7, T13, T7.72, T7.57] +- **agent_type**: worker +- **ownership**: `src/telemetry/collectors/linux_network.c`, `src/telemetry/collectors/linux_process.c`, `src/telemetry/collectors/linux_thermal.c`, `tests/unit/test_t7_95.c`, `tests/unit/cmake/eng/t7_95.cmake`, `quality/evidence/T7.95.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-8d33cee7806f5d43, CLANGTIDY-02b18b5e0f4e3b93, CLANGTIDY-b84c577b6e369ac3, CLANGTIDY-8b66085f0250d8e1, CLANGTIDY-15838d8765a7623e, CLANGTIDY-df8a271dac7dc00c +- **acceptance_criteria**: + - Exercise signed/unsigned boundaries, negative/zero values, maximum accepted values and multiplication overflow before conversion; assert valid results are unchanged and invalid values cannot wrap, divide by zero or allocate oversized memory. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.95 --regex '^test_t7_95$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.95`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.96: bugprone-implicit-widening-of-multiplication-result in ffmpeg_rss_guard.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/tools/ffmpeg_rss_guard.c`, `tests/unit/test_t7_96.c`, `tests/unit/cmake/eng/t7_96.cmake`, `quality/evidence/T7.96.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-802a0570e39a96a6, CLANGTIDY-a072eb98578bd9cf +- **acceptance_criteria**: + - Exercise signed/unsigned boundaries, negative/zero values, maximum accepted values and multiplication overflow before conversion; assert valid results are unchanged and invalid values cannot wrap, divide by zero or allocate oversized memory. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.96 --regex '^test_t7_96$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.96`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.97: bugprone-implicit-widening-of-multiplication-result in lpr_crypto.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/utils/lpr_crypto.c`, `tests/unit/test_t7_97.c`, `tests/unit/cmake/eng/t7_97.cmake`, `quality/evidence/T7.97.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-576d7acbaee5cef6 +- **acceptance_criteria**: + - Exercise signed/unsigned boundaries, negative/zero values, maximum accepted values and multiplication overflow before conversion; assert valid results are unchanged and invalid values cannot wrap, divide by zero or allocate oversized memory. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.97 --regex '^test_t7_97$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.97`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.98: bugprone-implicit-widening-of-multiplication-result in go2rtc_api.c, go2rtc_consumer.c, go2rtc_integration.c, go2rtc_snapshot.c, go2rtc_stream.c +- **depends_on**: [T7, T13, T7.40] +- **agent_type**: worker +- **ownership**: `src/video/go2rtc/go2rtc_api.c`, `src/video/go2rtc/go2rtc_consumer.c`, `src/video/go2rtc/go2rtc_integration.c`, `src/video/go2rtc/go2rtc_snapshot.c`, `src/video/go2rtc/go2rtc_stream.c`, `tests/unit/test_t7_98.c`, `tests/unit/cmake/eng/t7_98.cmake`, `quality/evidence/T7.98.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-eadc49a35e808a77, CLANGTIDY-bdcbae59a275ce50, CLANGTIDY-5582e7df3dd25730, CLANGTIDY-23826be3117b0a5e, CLANGTIDY-cac166160ef5ab94, CLANGTIDY-6eced00c619f459a, CLANGTIDY-a35257b195053d18, CLANGTIDY-9fbcc527095ebbbb, CLANGTIDY-c263543788ddaa36, CLANGTIDY-d06458b2bdfca56d, CLANGTIDY-1592d34d4e3dc54c, CLANGTIDY-957e3e8e5e143726, CLANGTIDY-76ebb6a603073b54, CLANGTIDY-2c1116134a2ee5a3, CLANGTIDY-0ce2e7833fc885fd, CLANGTIDY-4e94aa9ff957dec6, CLANGTIDY-f6ddc3b14e23e020 +- **acceptance_criteria**: + - Exercise signed/unsigned boundaries, negative/zero values, maximum accepted values and multiplication overflow before conversion; assert valid results are unchanged and invalid values cannot wrap, divide by zero or allocate oversized memory. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.98 --regex '^test_t7_98$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.98`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.99: bugprone-implicit-widening-of-multiplication-result in mp4_writer_utils.c +- **depends_on**: [T7, T13, T7.77] +- **agent_type**: worker +- **ownership**: `src/video/mp4_writer_utils.c`, `tests/unit/test_t7_99.c`, `tests/unit/cmake/eng/t7_99.cmake`, `quality/evidence/T7.99.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-a19387fbb98806ae +- **acceptance_criteria**: + - Exercise signed/unsigned boundaries, negative/zero values, maximum accepted values and multiplication overflow before conversion; assert valid results are unchanged and invalid values cannot wrap, divide by zero or allocate oversized memory. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.99 --regex '^test_t7_99$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.99`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.100: bugprone-implicit-widening-of-multiplication-result in onvif_detection.c +- **depends_on**: [T7, T13, T7.18] +- **agent_type**: worker +- **ownership**: `src/video/onvif_detection.c`, `tests/unit/test_t7_100.c`, `tests/unit/cmake/eng/t7_100.cmake`, `quality/evidence/T7.100.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-18362aef7fade326, CLANGTIDY-eade1f2d38582a47, CLANGTIDY-8b074e54e83681ff +- **acceptance_criteria**: + - Exercise signed/unsigned boundaries, negative/zero values, maximum accepted values and multiplication overflow before conversion; assert valid results are unchanged and invalid values cannot wrap, divide by zero or allocate oversized memory. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.100 --regex '^test_t7_100$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.100`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.101: bugprone-implicit-widening-of-multiplication-result in onvif_event.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/video/onvif_event.c`, `tests/unit/test_t7_101.c`, `tests/unit/cmake/eng/t7_101.cmake`, `quality/evidence/T7.101.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-eb4e649dccdb530f +- **acceptance_criteria**: + - Exercise signed/unsigned boundaries, negative/zero values, maximum accepted values and multiplication overflow before conversion; assert valid results are unchanged and invalid values cannot wrap, divide by zero or allocate oversized memory. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.101 --regex '^test_t7_101$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.101`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.102: bugprone-implicit-widening-of-multiplication-result in api_handlers_investigations.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_investigations.c`, `tests/unit/test_t7_102.c`, `tests/unit/cmake/eng/t7_102.cmake`, `quality/evidence/T7.102.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-6829e02764d9d278, CLANGTIDY-113735d7300e4f01, CLANGTIDY-25b00beae5a3f4a8, CLANGTIDY-2387b22f15e219d3 +- **acceptance_criteria**: + - Exercise signed/unsigned boundaries, negative/zero values, maximum accepted values and multiplication overflow before conversion; assert valid results are unchanged and invalid values cannot wrap, divide by zero or allocate oversized memory. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.102 --regex '^test_t7_102$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.102`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.103: bugprone-implicit-widening-of-multiplication-result in api_handlers_lpr.c +- **depends_on**: [T7, T13, T7.45] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_lpr.c`, `tests/unit/test_t7_103.c`, `tests/unit/cmake/eng/t7_103.cmake`, `quality/evidence/T7.103.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-0818b8d62b6628c5 +- **acceptance_criteria**: + - Exercise signed/unsigned boundaries, negative/zero values, maximum accepted values and multiplication overflow before conversion; assert valid results are unchanged and invalid values cannot wrap, divide by zero or allocate oversized memory. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.103 --regex '^test_t7_103$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.103`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.104: bugprone-implicit-widening-of-multiplication-result in api_handlers_operator_floor_plans.c +- **depends_on**: [T7, T13, T7.27] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_operator_floor_plans.c`, `tests/unit/test_t7_104.c`, `tests/unit/cmake/eng/t7_104.cmake`, `quality/evidence/T7.104.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-53e88d8983baf290, CLANGTIDY-dbd6a5de3a7e191c +- **acceptance_criteria**: + - Exercise signed/unsigned boundaries, negative/zero values, maximum accepted values and multiplication overflow before conversion; assert valid results are unchanged and invalid values cannot wrap, divide by zero or allocate oversized memory. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.104 --regex '^test_t7_104$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.104`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.105: bugprone-implicit-widening-of-multiplication-result in api_handlers_storage.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_storage.c`, `tests/unit/test_t7_105.c`, `tests/unit/cmake/eng/t7_105.cmake`, `quality/evidence/T7.105.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-88f84938e1b8feb0 +- **acceptance_criteria**: + - Exercise signed/unsigned boundaries, negative/zero values, maximum accepted values and multiplication overflow before conversion; assert valid results are unchanged and invalid values cannot wrap, divide by zero or allocate oversized memory. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.105 --regex '^test_t7_105$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.105`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.106: bugprone-implicit-widening-of-multiplication-result in api_handlers_system_go2rtc.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_system_go2rtc.c`, `tests/unit/test_t7_106.c`, `tests/unit/cmake/eng/t7_106.cmake`, `quality/evidence/T7.106.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-101a7d6e0cfda715, CLANGTIDY-c586f58fa85c527e +- **acceptance_criteria**: + - Exercise signed/unsigned boundaries, negative/zero values, maximum accepted values and multiplication overflow before conversion; assert valid results are unchanged and invalid values cannot wrap, divide by zero or allocate oversized memory. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.106 --regex '^test_t7_106$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.106`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.107: bugprone-implicit-widening-of-multiplication-result in api_handlers_timeline.c +- **depends_on**: [T7, T13, T7.85] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_timeline.c`, `tests/unit/test_t7_107.c`, `tests/unit/cmake/eng/t7_107.cmake`, `quality/evidence/T7.107.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-18895086ef6f0ed9 +- **acceptance_criteria**: + - Exercise signed/unsigned boundaries, negative/zero values, maximum accepted values and multiplication overflow before conversion; assert valid results are unchanged and invalid values cannot wrap, divide by zero or allocate oversized memory. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.107 --regex '^test_t7_107$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.107`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.108: bugprone-implicit-widening-of-multiplication-result in go2rtc_websocket.c +- **depends_on**: [T7, T13, T7.82] +- **agent_type**: worker +- **ownership**: `src/web/go2rtc_websocket.c`, `tests/unit/test_t7_108.c`, `tests/unit/cmake/eng/t7_108.cmake`, `quality/evidence/T7.108.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-6e45cad03ed5a9f3 +- **acceptance_criteria**: + - Exercise signed/unsigned boundaries, negative/zero values, maximum accepted values and multiplication overflow before conversion; assert valid results are unchanged and invalid values cannot wrap, divide by zero or allocate oversized memory. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.108 --regex '^test_t7_108$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.108`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.109: bugprone-implicit-widening-of-multiplication-result in recording_archive.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/web/recording_archive.c`, `tests/unit/test_t7_109.c`, `tests/unit/cmake/eng/t7_109.cmake`, `quality/evidence/T7.109.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-5bcac8af8164c1b0, CLANGTIDY-e2b358165654b79e +- **acceptance_criteria**: + - Exercise signed/unsigned boundaries, negative/zero values, maximum accepted values and multiplication overflow before conversion; assert valid results are unchanged and invalid values cannot wrap, divide by zero or allocate oversized memory. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.109 --regex '^test_t7_109$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.109`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.110: bugprone-inc-dec-in-conditions in storage_source.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/storage/storage_source.c`, `tests/unit/test_t7_110.c`, `tests/unit/cmake/eng/t7_110.cmake`, `quality/evidence/T7.110.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-9613b39c2e0a1e4c +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.110 --regex '^test_t7_110$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.110`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.111: bugprone-macro-parentheses in cJSON.h +- **depends_on**: [T7, T13] +- **agent_type**: local +- **ownership**: `quality/reviews/t7_111-upstream.json`, `quality/evidence/T7.111.md`. +- **priority**: low +- **description**: Review pinned upstream source and reproduce the listed diagnostics in the actual linked feature profile. Record precise upstream fix/version or evidence-backed false-positive analysis. Do not edit system headers or vendored/submodule source as an untracked patch. If an update/local patch is needed, append an exact bounded integration child, dependency and regression command to T33 before closing this task. Missing upstream or feature evidence blocks completion. +- **findings**: CLANGTIDY-5f6ae30cde3aaf3d, CLANGTIDY-bc48f96894493869, CLANGTIDY-01e4c541adfa23e3, CLANGTIDY-3abdad2ed8a71fe5, CLANGTIDY-09829500c354c2b5, CLANGTIDY-661c1d9d010f4873, CLANGTIDY-f69ef126d8e55b2b, CLANGTIDY-6d1385324d08479d +- **acceptance_criteria**: + - Compile/evaluate precedence-sensitive macro arguments and side effects; results must match the intended fully parenthesized expression without double evaluation. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 -m json.tool quality/reviews/t7_111-upstream.json` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.111`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.112: bugprone-macro-parentheses in api_detection.c +- **depends_on**: [T7, T13, T7.80] +- **agent_type**: worker +- **ownership**: `src/video/api_detection.c`, `tests/unit/test_t7_112.c`, `tests/unit/cmake/eng/t7_112.cmake`, `quality/evidence/T7.112.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-ae25a6959341dd10 +- **acceptance_criteria**: + - Compile/evaluate precedence-sensitive macro arguments and side effects; results must match the intended fully parenthesized expression without double evaluation. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.112 --regex '^test_t7_112$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.112`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.113: bugprone-misplaced-widening-cast in db_fleet_query.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/database/db_fleet_query.c`, `tests/unit/test_t7_113.c`, `tests/unit/cmake/eng/t7_113.cmake`, `quality/evidence/T7.113.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-7bff304a77769e6c +- **acceptance_criteria**: + - Exercise signed/unsigned boundaries, negative/zero values, maximum accepted values and multiplication overflow before conversion; assert valid results are unchanged and invalid values cannot wrap, divide by zero or allocate oversized memory. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.113 --regex '^test_t7_113$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.113`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.114: bugprone-multi-level-implicit-pointer-conversion in authorization.c +- **depends_on**: [T7, T13, T7.53] +- **agent_type**: worker +- **ownership**: `src/core/authorization.c`, `tests/unit/test_t7_114.c`, `tests/unit/cmake/eng/t7_114.cmake`, `quality/evidence/T7.114.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-4505cfde8bb2e42a, CLANGTIDY-ff0e443bb0b29339 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.114 --regex '^test_t7_114$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.114`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.115: bugprone-multi-level-implicit-pointer-conversion in camera_collection_filter.c +- **depends_on**: [T7, T13, T7.33] +- **agent_type**: worker +- **ownership**: `src/core/camera_collection_filter.c`, `tests/unit/test_t7_115.c`, `tests/unit/cmake/eng/t7_115.cmake`, `quality/evidence/T7.115.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-3a7569f24a5603a4, CLANGTIDY-54a9eba3b05f0872, CLANGTIDY-a221992baafec96f +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.115 --regex '^test_t7_115$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.115`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.116: bugprone-multi-level-implicit-pointer-conversion in camera_selector.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/core/camera_selector.c`, `tests/unit/test_t7_116.c`, `tests/unit/cmake/eng/t7_116.cmake`, `quality/evidence/T7.116.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-8e1debc0e42f328a, CLANGTIDY-1fb6fbd990b73e55, CLANGTIDY-52e7ee5b7b20725e, CLANGTIDY-a816d3388754e9a3, CLANGTIDY-bdf2049affa5a3fd, CLANGTIDY-946781b07a8a19f6 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.116 --regex '^test_t7_116$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.116`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.117: bugprone-multi-level-implicit-pointer-conversion in db_storage_lifecycle.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/database/db_storage_lifecycle.c`, `tests/unit/test_t7_117.c`, `tests/unit/cmake/eng/t7_117.cmake`, `quality/evidence/T7.117.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-39f0ad6017069f02, CLANGTIDY-c666354bdbc1df4b, CLANGTIDY-c8b906651c5b2c7f, CLANGTIDY-8a2d068dc3e8b0c0, CLANGTIDY-ad7afdbd70ca0a46 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.117 --regex '^test_t7_117$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.117`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.118: bugprone-multi-level-implicit-pointer-conversion in hls_cache_cleanup.c +- **depends_on**: [T7, T13, T7.71] +- **agent_type**: worker +- **ownership**: `src/video/hls/hls_cache_cleanup.c`, `tests/unit/test_t7_118.c`, `tests/unit/cmake/eng/t7_118.cmake`, `quality/evidence/T7.118.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-48d30c8fb35cc9cd, CLANGTIDY-1634947d8289644f, CLANGTIDY-cb9a5afe872113f7, CLANGTIDY-098d117140abdf3a, CLANGTIDY-a89af13dd80ae187 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.118 --regex '^test_t7_118$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.118`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.119: bugprone-multi-level-implicit-pointer-conversion in mp4_segment_recorder.c +- **depends_on**: [T7, T13, T28.2] +- **agent_type**: worker +- **ownership**: `src/video/mp4_segment_recorder.c`, `tests/unit/test_t7_119.c`, `tests/unit/cmake/eng/t7_119.cmake`, `quality/evidence/T7.119.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-30c3f7edea8fbd8d +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.119 --regex '^test_t7_119$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.119`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.120: bugprone-multi-level-implicit-pointer-conversion in mp4_writer_core.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/video/mp4_writer_core.c`, `tests/unit/test_t7_120.c`, `tests/unit/cmake/eng/t7_120.cmake`, `quality/evidence/T7.120.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-d421e791a6b63cc9 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.120 --regex '^test_t7_120$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.120`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.121: bugprone-multi-level-implicit-pointer-conversion in mp4_writer_utils.c +- **depends_on**: [T7, T13, T7.99] +- **agent_type**: worker +- **ownership**: `src/video/mp4_writer_utils.c`, `tests/unit/test_t7_121.c`, `tests/unit/cmake/eng/t7_121.cmake`, `quality/evidence/T7.121.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-c536433a0089e4b9, CLANGTIDY-4e34d6b524364f03 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.121 --regex '^test_t7_121$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.121`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.122: bugprone-multi-level-implicit-pointer-conversion in unified_detection_thread.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/video/unified_detection_thread.c`, `tests/unit/test_t7_122.c`, `tests/unit/cmake/eng/t7_122.cmake`, `quality/evidence/T7.122.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-5dc4623d12ccf5b2 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.122 --regex '^test_t7_122$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.122`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.123: bugprone-multi-level-implicit-pointer-conversion in api_handlers_camera_collections.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_camera_collections.c`, `tests/unit/test_t7_123.c`, `tests/unit/cmake/eng/t7_123.cmake`, `quality/evidence/T7.123.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-1f9c86e17ca383f9, CLANGTIDY-316e32d23b15b780, CLANGTIDY-7464eb7864628fd1, CLANGTIDY-5e2b3b364d163173, CLANGTIDY-81fbfb962103b645, CLANGTIDY-c41e983019497e88, CLANGTIDY-308b0349ef001b56, CLANGTIDY-7a3f68ca1e19c9db, CLANGTIDY-475d0f82a4e9b764, CLANGTIDY-2276a7bff678cf41, CLANGTIDY-63c736995704d2d6, CLANGTIDY-f5ac788b77f1417a, CLANGTIDY-ae6a96ea4d41087d, CLANGTIDY-f617ccfb4cfc3cc2, CLANGTIDY-169001b0559c7bcb, CLANGTIDY-6bce10f282924906 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.123 --regex '^test_t7_123$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.123`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.124: bugprone-multi-level-implicit-pointer-conversion in api_handlers_fleet.c +- **depends_on**: [T7, T13, T7.63] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_fleet.c`, `tests/unit/test_t7_124.c`, `tests/unit/cmake/eng/t7_124.cmake`, `quality/evidence/T7.124.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-0b0bcefaf7a0b24f, CLANGTIDY-d8c6998c9398adf1, CLANGTIDY-79ebe40a23b7323a, CLANGTIDY-6da59a8629d16699, CLANGTIDY-773e04e041de5410, CLANGTIDY-f12d6e4a615966e9, CLANGTIDY-ede4da279d79ec69, CLANGTIDY-36c8164a04c2420c, CLANGTIDY-4053884d5188ab2f +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.124 --regex '^test_t7_124$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.124`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.125: bugprone-multi-level-implicit-pointer-conversion in api_handlers_retention.c +- **depends_on**: [T7, T13, T7.47] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_retention.c`, `tests/unit/test_t7_125.c`, `tests/unit/cmake/eng/t7_125.cmake`, `quality/evidence/T7.125.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-0cc9a9ea64babfdc, CLANGTIDY-234cb88ea5651731 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.125 --regex '^test_t7_125$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.125`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.126: bugprone-multi-level-implicit-pointer-conversion in api_handlers_storage_policies.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_storage_policies.c`, `tests/unit/test_t7_126.c`, `tests/unit/cmake/eng/t7_126.cmake`, `quality/evidence/T7.126.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-312e6eafc5ebdca6, CLANGTIDY-1985156ff8e5c87d, CLANGTIDY-ca7e963b9f3e3214, CLANGTIDY-aede9d1024b66426, CLANGTIDY-07c63776b48ad48f, CLANGTIDY-ea16598ca26b9108 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.126 --regex '^test_t7_126$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.126`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.127: bugprone-narrowing-conversions in api_timing.h +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `include/web/api_timing.h`, `tests/unit/test_t7_127.c`, `tests/unit/cmake/eng/t7_127.cmake`, `quality/evidence/T7.127.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-ca2f4806dea5f177, CLANGTIDY-054f4f2a2583f814 +- **acceptance_criteria**: + - Exercise signed/unsigned boundaries, negative/zero values, maximum accepted values and multiplication overflow before conversion; assert valid results are unchanged and invalid values cannot wrap, divide by zero or allocate oversized memory. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.127 --regex '^test_t7_127$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.127`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.128: bugprone-narrowing-conversions in path_utils.c +- **depends_on**: [T7, T13, T7.52, T7.2.8] +- **agent_type**: worker +- **ownership**: `src/core/path_utils.c`, `tests/unit/test_t7_128.c`, `tests/unit/cmake/eng/t7_128.cmake`, `quality/evidence/T7.128.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-dda225859c807d72 +- **acceptance_criteria**: + - Exercise signed/unsigned boundaries, negative/zero values, maximum accepted values and multiplication overflow before conversion; assert valid results are unchanged and invalid values cannot wrap, divide by zero or allocate oversized memory. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.128 --regex '^test_t7_128$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.128`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.129: bugprone-narrowing-conversions in db_storage_lifecycle.c +- **depends_on**: [T7, T13, T7.117] +- **agent_type**: worker +- **ownership**: `src/database/db_storage_lifecycle.c`, `tests/unit/test_t7_129.c`, `tests/unit/cmake/eng/t7_129.cmake`, `quality/evidence/T7.129.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-76e54a4e3ac5efb3, CLANGTIDY-84fbbd387f95947a +- **acceptance_criteria**: + - Exercise signed/unsigned boundaries, negative/zero values, maximum accepted values and multiplication overflow before conversion; assert valid results are unchanged and invalid values cannot wrap, divide by zero or allocate oversized memory. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.129 --regex '^test_t7_129$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.129`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.130: bugprone-narrowing-conversions in mp4_writer_utils.c +- **depends_on**: [T7, T13, T7.121] +- **agent_type**: worker +- **ownership**: `src/video/mp4_writer_utils.c`, `tests/unit/test_t7_130.c`, `tests/unit/cmake/eng/t7_130.cmake`, `quality/evidence/T7.130.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-c74d7d04edeb4ee2, CLANGTIDY-cb47dbd2bd0937a1, CLANGTIDY-6f7bee0a60d5152f +- **acceptance_criteria**: + - Exercise signed/unsigned boundaries, negative/zero values, maximum accepted values and multiplication overflow before conversion; assert valid results are unchanged and invalid values cannot wrap, divide by zero or allocate oversized memory. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.130 --regex '^test_t7_130$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.130`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.131: bugprone-narrowing-conversions in stream_frame_rate.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/video/stream_frame_rate.c`, `tests/unit/test_t7_131.c`, `tests/unit/cmake/eng/t7_131.cmake`, `quality/evidence/T7.131.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-e1b115ce18cb0926, CLANGTIDY-9bb912a1e3d2bf88 +- **acceptance_criteria**: + - Exercise signed/unsigned boundaries, negative/zero values, maximum accepted values and multiplication overflow before conversion; assert valid results are unchanged and invalid values cannot wrap, divide by zero or allocate oversized memory. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.131 --regex '^test_t7_131$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.131`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.132: bugprone-narrowing-conversions in api_handlers_storage_compliance.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_storage_compliance.c`, `tests/unit/test_t7_132.c`, `tests/unit/cmake/eng/t7_132.cmake`, `quality/evidence/T7.132.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-6a756d858b2a4590, CLANGTIDY-0f6724ca27b42ed9, CLANGTIDY-ec3e72154d735330, CLANGTIDY-e85c88c08460d2ef, CLANGTIDY-9c90ce2f531301e0, CLANGTIDY-3e6ff569aac7699e, CLANGTIDY-ca4b8a2f2a2f60bf, CLANGTIDY-c990523bb862f2b6, CLANGTIDY-7a5a584e25342a04, CLANGTIDY-8d9dd908cb4d8452 +- **acceptance_criteria**: + - Exercise signed/unsigned boundaries, negative/zero values, maximum accepted values and multiplication overflow before conversion; assert valid results are unchanged and invalid values cannot wrap, divide by zero or allocate oversized memory. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.132 --regex '^test_t7_132$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.132`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.133: bugprone-narrowing-conversions in httpd_utils.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/web/httpd_utils.c`, `tests/unit/test_t7_133.c`, `tests/unit/cmake/eng/t7_133.cmake`, `quality/evidence/T7.133.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-cb7511d8b0a3b651 +- **acceptance_criteria**: + - Exercise signed/unsigned boundaries, negative/zero values, maximum accepted values and multiplication overflow before conversion; assert valid results are unchanged and invalid values cannot wrap, divide by zero or allocate oversized memory. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.133 --regex '^test_t7_133$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.133`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.134: bugprone-not-null-terminated-result in mqtt_destination_client.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/core/mqtt_destination_client.c`, `tests/unit/test_t7_134.c`, `tests/unit/cmake/eng/t7_134.cmake`, `quality/evidence/T7.134.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-d7edea27d80824ed +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.134 --regex '^test_t7_134$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.134`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.135: bugprone-not-null-terminated-result in linux_hardware.c +- **depends_on**: [T7, T13, T7.58] +- **agent_type**: worker +- **ownership**: `src/telemetry/providers/linux_hardware.c`, `tests/unit/test_t7_135.c`, `tests/unit/cmake/eng/t7_135.cmake`, `quality/evidence/T7.135.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-65ab86fae5fd2213 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.135 --regex '^test_t7_135$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.135`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.136: bugprone-suspicious-string-compare in db_storage_targets.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/database/db_storage_targets.c`, `tests/unit/test_t7_136.c`, `tests/unit/cmake/eng/t7_136.cmake`, `quality/evidence/T7.136.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-709dbb22240f409f, CLANGTIDY-d68060f1555ca324, CLANGTIDY-b922ced0e34aacea, CLANGTIDY-295d32efbfee9145 +- **acceptance_criteria**: + - Use equal, unequal, prefix-only and empty strings to prove the branch implements the intended comparison rather than pointer/boolean equality. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.136 --regex '^test_t7_136$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.136`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.137: bugprone-suspicious-string-compare in storage_deletion.c +- **depends_on**: [T7, T13, T28.1, T29.3] +- **agent_type**: worker +- **ownership**: `src/storage/storage_deletion.c`, `tests/unit/test_t7_137.c`, `tests/unit/cmake/eng/t7_137.cmake`, `quality/evidence/T7.137.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-56955e0bf792f087 +- **acceptance_criteria**: + - Use equal, unequal, prefix-only and empty strings to prove the branch implements the intended comparison rather than pointer/boolean equality. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.137 --regex '^test_t7_137$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.137`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.138: bugprone-suspicious-string-compare in storage_migration.c +- **depends_on**: [T7, T13, T7.93] +- **agent_type**: worker +- **ownership**: `src/storage/storage_migration.c`, `tests/unit/test_t7_138.c`, `tests/unit/cmake/eng/t7_138.cmake`, `quality/evidence/T7.138.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-d6379e87cfa699fa +- **acceptance_criteria**: + - Use equal, unequal, prefix-only and empty strings to prove the branch implements the intended comparison rather than pointer/boolean equality. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.138 --regex '^test_t7_138$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.138`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.139: bugprone-suspicious-string-compare in storage_s3.c +- **depends_on**: [T7, T13, T7.94] +- **agent_type**: worker +- **ownership**: `src/storage/storage_s3.c`, `tests/unit/test_t7_139.c`, `tests/unit/cmake/eng/t7_139.cmake`, `quality/evidence/T7.139.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-789d9f9254afa1b1, CLANGTIDY-2f98dec54b741594, CLANGTIDY-852f97f8aacc5432, CLANGTIDY-ba57287cdb3eb096, CLANGTIDY-bfbbf95e2bedfe38, CLANGTIDY-65cc9415acf1be4d, CLANGTIDY-5561b78de6be772b, CLANGTIDY-6b719694dd2640c7 +- **acceptance_criteria**: + - Use equal, unequal, prefix-only and empty strings to prove the branch implements the intended comparison rather than pointer/boolean equality. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.139 --regex '^test_t7_139$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.139`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.140: bugprone-suspicious-string-compare in storage_source.c +- **depends_on**: [T7, T13, T7.110] +- **agent_type**: worker +- **ownership**: `src/storage/storage_source.c`, `tests/unit/test_t7_140.c`, `tests/unit/cmake/eng/t7_140.cmake`, `quality/evidence/T7.140.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-6a07b625f331b20e +- **acceptance_criteria**: + - Use equal, unequal, prefix-only and empty strings to prove the branch implements the intended comparison rather than pointer/boolean equality. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.140 --regex '^test_t7_140$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.140`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.141: bugprone-suspicious-string-compare in recording_archive.c +- **depends_on**: [T7, T13, T7.109] +- **agent_type**: worker +- **ownership**: `src/web/recording_archive.c`, `tests/unit/test_t7_141.c`, `tests/unit/cmake/eng/t7_141.cmake`, `quality/evidence/T7.141.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-adf6edf7670e9010 +- **acceptance_criteria**: + - Use equal, unequal, prefix-only and empty strings to prove the branch implements the intended comparison rather than pointer/boolean equality. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.141 --regex '^test_t7_141$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.141`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.142: bugprone-unsafe-functions,cert-msc24-c,cert-msc33-c in storage_s3.c +- **depends_on**: [T7, T13, T7.139] +- **agent_type**: worker +- **ownership**: `src/storage/storage_s3.c`, `tests/unit/test_t7_142.c`, `tests/unit/cmake/eng/t7_142.cmake`, `quality/evidence/T7.142.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-3a081de1dd30ebb2 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.142 --regex '^test_t7_142$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.142`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.143: cert-err34-c in linux_device_map.c, linux_hardware.c +- **depends_on**: [T7, T13, T7.135] +- **agent_type**: worker +- **ownership**: `src/telemetry/providers/linux_device_map.c`, `src/telemetry/providers/linux_hardware.c`, `tests/unit/test_t7_143.c`, `tests/unit/cmake/eng/t7_143.cmake`, `quality/evidence/T7.143.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-c52ffccc8c0fb590, CLANGTIDY-811ab08155776051 +- **acceptance_criteria**: + - Exercise empty, trailing-junk, negative, out-of-range and valid numeric strings; conversion must check end pointer and range before applying configuration. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.143 --regex '^test_t7_143$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.143`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.144: cert-err34-c in ffmpeg_rss_guard.c +- **depends_on**: [T7, T13, T7.96] +- **agent_type**: worker +- **ownership**: `src/tools/ffmpeg_rss_guard.c`, `tests/unit/test_t7_144.c`, `tests/unit/cmake/eng/t7_144.cmake`, `quality/evidence/T7.144.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-11d1a497d98b8490 +- **acceptance_criteria**: + - Exercise empty, trailing-junk, negative, out-of-range and valid numeric strings; conversion must check end pointer and range before applying configuration. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.144 --regex '^test_t7_144$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.144`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.145: cert-err34-c in go2rtc_integration.c, go2rtc_process.c +- **depends_on**: [T7, T13, T7.98, T7.60] +- **agent_type**: worker +- **ownership**: `src/video/go2rtc/go2rtc_integration.c`, `src/video/go2rtc/go2rtc_process.c`, `tests/unit/test_t7_145.c`, `tests/unit/cmake/eng/t7_145.cmake`, `quality/evidence/T7.145.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-a7a71d348e2c49f5, CLANGTIDY-41876b44c3442c4b, CLANGTIDY-3a3cc8c9282e0fb5, CLANGTIDY-43d0d5170d6a8434 +- **acceptance_criteria**: + - Exercise empty, trailing-junk, negative, out-of-range and valid numeric strings; conversion must check end pointer and range before applying configuration. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.145 --regex '^test_t7_145$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.145`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.146: cert-err34-c in onvif_detection.c +- **depends_on**: [T7, T13, T7.100] +- **agent_type**: worker +- **ownership**: `src/video/onvif_detection.c`, `tests/unit/test_t7_146.c`, `tests/unit/cmake/eng/t7_146.cmake`, `quality/evidence/T7.146.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-a6e1105b549e0cf0 +- **acceptance_criteria**: + - Exercise empty, trailing-junk, negative, out-of-range and valid numeric strings; conversion must check end pointer and range before applying configuration. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.146 --regex '^test_t7_146$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.146`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.147: cert-err34-c in api_handlers_go2rtc_proxy.c +- **depends_on**: [T7, T13, T7.67] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_go2rtc_proxy.c`, `tests/unit/test_t7_147.c`, `tests/unit/cmake/eng/t7_147.cmake`, `quality/evidence/T7.147.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-ab5e571ba64af645 +- **acceptance_criteria**: + - Exercise empty, trailing-junk, negative, out-of-range and valid numeric strings; conversion must check end pointer and range before applying configuration. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.147 --regex '^test_t7_147$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.147`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.148: cert-err34-c in api_handlers_investigations.c +- **depends_on**: [T7, T13, T7.102] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_investigations.c`, `tests/unit/test_t7_148.c`, `tests/unit/cmake/eng/t7_148.cmake`, `quality/evidence/T7.148.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-e5ec24184e5b758c +- **acceptance_criteria**: + - Exercise empty, trailing-junk, negative, out-of-range and valid numeric strings; conversion must check end pointer and range before applying configuration. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.148 --regex '^test_t7_148$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.148`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.149: cert-err34-c in api_handlers_system_logs.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_system_logs.c`, `tests/unit/test_t7_149.c`, `tests/unit/cmake/eng/t7_149.cmake`, `quality/evidence/T7.149.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-0fda904c59353043 +- **acceptance_criteria**: + - Exercise empty, trailing-junk, negative, out-of-range and valid numeric strings; conversion must check end pointer and range before applying configuration. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.149 --regex '^test_t7_149$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.149`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.150: cert-err34-c in go2rtc_websocket.c +- **depends_on**: [T7, T13, T7.108] +- **agent_type**: worker +- **ownership**: `src/web/go2rtc_websocket.c`, `tests/unit/test_t7_150.c`, `tests/unit/cmake/eng/t7_150.cmake`, `quality/evidence/T7.150.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-5c42b38e8a9c25ff +- **acceptance_criteria**: + - Exercise empty, trailing-junk, negative, out-of-range and valid numeric strings; conversion must check end pointer and range before applying configuration. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.150 --regex '^test_t7_150$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.150`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.151: cert-int09-c in authorization.h +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `include/core/authorization.h`, `tests/unit/test_t7_151.c`, `tests/unit/cmake/eng/t7_151.cmake`, `quality/evidence/T7.151.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-1539a5c009afd0df +- **acceptance_criteria**: + - Verify enum constants remain representable and unique on the supported ABI without changing public numeric values. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.151 --regex '^test_t7_151$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.151`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.152: cert-int09-c in faac.h +- **depends_on**: [T7, T13] +- **agent_type**: local +- **ownership**: `quality/reviews/t7_152-upstream.json`, `quality/evidence/T7.152.md`. +- **priority**: low +- **description**: Review pinned upstream source and reproduce the listed diagnostics in the actual linked feature profile. Record precise upstream fix/version or evidence-backed false-positive analysis. Do not edit system headers or vendored/submodule source as an untracked patch. If an update/local patch is needed, append an exact bounded integration child, dependency and regression command to T33 before closing this task. Missing upstream or feature evidence blocks completion. +- **findings**: CLANGTIDY-b6947bb24c18c8ac, CLANGTIDY-ddf685d0be472b86, CLANGTIDY-296d5f7499179bc0, CLANGTIDY-4c6957160cb5ae0c +- **acceptance_criteria**: + - Verify enum constants remain representable and unique on the supported ABI without changing public numeric values. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 -m json.tool quality/reviews/t7_152-upstream.json` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.152`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.153: clang-analyzer-deadcode.DeadStores in db_system_health_incidents.c +- **depends_on**: [T7, T13, T7.79] +- **agent_type**: worker +- **ownership**: `src/database/db_system_health_incidents.c`, `tests/unit/test_t7_153.c`, `tests/unit/cmake/eng/t7_153.cmake`, `quality/evidence/T7.153.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-fe8af642c27ce051 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.153 --regex '^test_t7_153$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.153`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.154: clang-analyzer-deadcode.DeadStores in go2rtc_integration.c +- **depends_on**: [T7, T13, T7.145] +- **agent_type**: worker +- **ownership**: `src/video/go2rtc/go2rtc_integration.c`, `tests/unit/test_t7_154.c`, `tests/unit/cmake/eng/t7_154.cmake`, `quality/evidence/T7.154.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-88fbdf8930f67549 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.154 --regex '^test_t7_154$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.154`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.155: clang-analyzer-deadcode.DeadStores in mp4_segment_recorder.c +- **depends_on**: [T7, T13, T7.119, T28.2] +- **agent_type**: worker +- **ownership**: `src/video/mp4_segment_recorder.c`, `tests/unit/test_t7_155.c`, `tests/unit/cmake/eng/t7_155.cmake`, `quality/evidence/T7.155.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-27ac318897664d8a +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.155 --regex '^test_t7_155$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.155`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.156: clang-analyzer-deadcode.DeadStores in onvif_ptz.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/video/onvif_ptz.c`, `tests/unit/test_t7_156.c`, `tests/unit/cmake/eng/t7_156.cmake`, `quality/evidence/T7.156.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-7d7d3a3c4f1824bd +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.156 --regex '^test_t7_156$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.156`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.157: clang-analyzer-deadcode.DeadStores in recording_path.c +- **depends_on**: [T7, T13, T27.1] +- **agent_type**: worker +- **ownership**: `src/video/recording_path.c`, `tests/unit/test_t7_157.c`, `tests/unit/cmake/eng/t7_157.cmake`, `quality/evidence/T7.157.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-44c350a8b7e2fd4a, CLANGTIDY-a7fa9aad275f9425 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.157 --regex '^test_t7_157$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.157`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.158: clang-analyzer-deadcode.DeadStores in api_handlers_health.c +- **depends_on**: [T7, T13, T7.44] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_health.c`, `tests/unit/test_t7_158.c`, `tests/unit/cmake/eng/t7_158.cmake`, `quality/evidence/T7.158.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CLANGTIDY-cf5f09b5a02a9bca +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.158 --regex '^test_t7_158$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.158`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.159: invalidPointerCast in sod.c +- **depends_on**: [T7, T13] +- **agent_type**: local +- **ownership**: `quality/reviews/t7_159-upstream.json`, `quality/evidence/T7.159.md`. +- **priority**: low +- **description**: Review pinned upstream source and reproduce the listed diagnostics in the actual linked feature profile. Record precise upstream fix/version or evidence-backed false-positive analysis. Do not edit system headers or vendored/submodule source as an untracked patch. If an update/local patch is needed, append an exact bounded integration child, dependency and regression command to T33 before closing this task. Missing upstream or feature evidence blocks completion. +- **findings**: CPPCHECK-7975c2d572bc32c0, CPPCHECK-291bebc3b95eca4e +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 -m json.tool quality/reviews/t7_159-upstream.json` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.159`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.160: jsx-a11y/anchor-is-valid in Header.jsx +- **depends_on**: [T7, T10] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/Header.jsx`, `web/tests/remediation/t7_160.spec.jsx`, `quality/evidence/T7.160.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-366138c5d194685c +- **acceptance_criteria**: + - Use valid navigable hrefs for links and buttons for actions; verify keyboard behavior and no unexpected page navigation. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_160` + - `mkdir -p build/eng-artifacts/T7.160` + - `web/node_modules/.bin/eslint web/js/components/preact/Header.jsx --format json --output-file build/eng-artifacts/T7.160/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.160/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/anchor-is-valid'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.160`; inspect the raw T1 report and trace the caller. +- **status**: Completed (commit `233859ab`) +- **log**: Header profile action is a native disabled-capable button; links retain hrefs. Eight explicit tests including actual Chromium keyboard controls pass (seven default Node cases). Exactly one mapped lint warning removed; types, both builds and43/66 budgets pass. +- **files edited/created**: + +### T7.161: jsx-a11y/click-events-have-key-events in GridPicker.jsx, HLSVideoCell.jsx, HealthView.jsx, MSEVideoCell.jsx, StreamCard.jsx, StreamsView.jsx +- **depends_on**: [T7, T10] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/GridPicker.jsx`, `web/js/components/preact/HLSVideoCell.jsx`, `web/js/components/preact/HealthView.jsx`, `web/js/components/preact/MSEVideoCell.jsx`, `web/js/components/preact/StreamCard.jsx`, `web/js/components/preact/StreamsView.jsx`, `web/tests/remediation/t7_161.spec.jsx`, `quality/evidence/T7.161.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-ed278d9594c2abdb, ESLINT-BUCKET-67fcff31d1a4cf75, ESLINT-BUCKET-a642f51dfd2a124a, ESLINT-BUCKET-404de092ea97cc43, ESLINT-BUCKET-6243e36ec0fdb5cd, ESLINT-BUCKET-5c63d53fbef3ba6d +- **acceptance_criteria**: + - Assert Enter and Space activation where the control semantics require it, focus visibility, no double dispatch, and unchanged pointer interaction. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_161` + - `mkdir -p build/eng-artifacts/T7.161` + - `web/node_modules/.bin/eslint web/js/components/preact/GridPicker.jsx web/js/components/preact/HLSVideoCell.jsx web/js/components/preact/HealthView.jsx web/js/components/preact/MSEVideoCell.jsx web/js/components/preact/StreamCard.jsx web/js/components/preact/StreamsView.jsx --format json --output-file build/eng-artifacts/T7.161/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.161/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/click-events-have-key-events'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.161`; inspect the raw T1 report and trace the caller. +- **status**: Completed (commit `d8552365`) +- **log**: All8assigned click/key warnings resolved via native actions/structural boundaries and hidden-control keyboard alternatives;9related removals, lint777→760with0errors/noadditions.19focused cases (9Chromium), full65suites/675passed before addedcapcontrol, types and final43/66assets/freshness pass. +- **files edited/created**: + +### T7.162: jsx-a11y/click-events-have-key-events in ToastContainer.jsx, UI.jsx, WebRTCVideoCell.jsx, ZoneEditor.jsx, LanguageSelector.jsx, ModalDialog.jsx +- **depends_on**: [T7, T10] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/ToastContainer.jsx`, `web/js/components/preact/UI.jsx`, `web/js/components/preact/WebRTCVideoCell.jsx`, `web/js/components/preact/ZoneEditor.jsx`, `web/js/components/preact/common/LanguageSelector.jsx`, `web/js/components/preact/common/ModalDialog.jsx`, `web/tests/remediation/t7_162.spec.jsx`, `quality/evidence/T7.162.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-ab2c863c82952346, ESLINT-BUCKET-f53a71c368c33ab9, ESLINT-BUCKET-45887456841ead8b, ESLINT-BUCKET-cb235968867c79e3, ESLINT-BUCKET-02418efc105b6ab9, ESLINT-BUCKET-4e8530c09d5fd082 +- **acceptance_criteria**: + - Assert Enter and Space activation where the control semantics require it, focus visibility, no double dispatch, and unchanged pointer interaction. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_162` + - `mkdir -p build/eng-artifacts/T7.162` + - `web/node_modules/.bin/eslint web/js/components/preact/ToastContainer.jsx web/js/components/preact/UI.jsx web/js/components/preact/WebRTCVideoCell.jsx web/js/components/preact/ZoneEditor.jsx web/js/components/preact/common/LanguageSelector.jsx web/js/components/preact/common/ModalDialog.jsx --format json --output-file build/eng-artifacts/T7.162/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.162/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/click-events-have-key-events'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.162`; inspect the raw T1 report and trace the caller. +- **status**: Completed +- **log**: 0080a39f resolves all eight assigned click/key warnings with native controls and structural boundaries. Eighteen focused cases including nine Chromium cases; full 66 suites/699 pass, types and modern43/default66 asset checks pass. Lint760→742, zero errors and no additions; related occurrences logged separately. +- **files edited/created**: + +### T7.163: jsx-a11y/click-events-have-key-events in LiveOperatorNavigator.jsx, RecordingsGrid.jsx, Go2rtcTab.jsx, ClearLogsModal.jsx, RestartModal.jsx, TimelinePlayer.jsx +- **depends_on**: [T7, T10, T10.2, T10.3] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/live/LiveOperatorNavigator.jsx`, `web/js/components/preact/recordings/RecordingsGrid.jsx`, `web/js/components/preact/settings/Go2rtcTab.jsx`, `web/js/components/preact/system/ClearLogsModal.jsx`, `web/js/components/preact/system/RestartModal.jsx`, `web/js/components/preact/timeline/TimelinePlayer.jsx`, `web/tests/remediation/t7_163.spec.jsx`, `quality/evidence/T7.163.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-f16c5e93d86bc6f9, ESLINT-BUCKET-41381cd9bd64279e, ESLINT-BUCKET-422485a76dc61a9b, ESLINT-BUCKET-44ea0e63c82a7151, ESLINT-BUCKET-155480cbfd270011, ESLINT-BUCKET-79b6e7adf4959fbc +- **acceptance_criteria**: + - Assert Enter and Space activation where the control semantics require it, focus visibility, no double dispatch, and unchanged pointer interaction. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_163` + - `mkdir -p build/eng-artifacts/T7.163` + - `web/node_modules/.bin/eslint web/js/components/preact/live/LiveOperatorNavigator.jsx web/js/components/preact/recordings/RecordingsGrid.jsx web/js/components/preact/settings/Go2rtcTab.jsx web/js/components/preact/system/ClearLogsModal.jsx web/js/components/preact/system/RestartModal.jsx web/js/components/preact/timeline/TimelinePlayer.jsx --format json --output-file build/eng-artifacts/T7.163/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.163/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/click-events-have-key-events'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.163`; inspect the raw T1 report and trace the caller. +- **status**: Completed (commit `29fa0d50`) +- **log**: Nine assigned click/key warnings and9related occurrences removed; native sibling media/location actions preserve existing callbacks and structural backdrop guards.95/95combined tests including actual browser, types, default/modern builds and43/66asset checks pass. No baseline or locale edits. +- **files edited/created**: + +### T7.164: jsx-a11y/click-events-have-key-events in AccessPolicyModal.jsx, AddUserModal.jsx, ApiKeyModal.jsx, AuditHistoryModal.jsx, DeleteUserModal.jsx, EditUserModal.jsx +- **depends_on**: [T7, T10] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/users/AccessPolicyModal.jsx`, `web/js/components/preact/users/AddUserModal.jsx`, `web/js/components/preact/users/ApiKeyModal.jsx`, `web/js/components/preact/users/AuditHistoryModal.jsx`, `web/js/components/preact/users/DeleteUserModal.jsx`, `web/js/components/preact/users/EditUserModal.jsx`, `web/tests/remediation/t7_164.spec.jsx`, `quality/evidence/T7.164.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-0a786d21e1ee7bd2, ESLINT-BUCKET-c2dc58af6543d1f5, ESLINT-BUCKET-3b71a4c47dfd1075, ESLINT-BUCKET-ea11ae604e62541e, ESLINT-BUCKET-8d573ae471211e73, ESLINT-BUCKET-22666b8c08cd887e +- **acceptance_criteria**: + - Assert Enter and Space activation where the control semantics require it, focus visibility, no double dispatch, and unchanged pointer interaction. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_164` + - `mkdir -p build/eng-artifacts/T7.164` + - `web/node_modules/.bin/eslint web/js/components/preact/users/AccessPolicyModal.jsx web/js/components/preact/users/AddUserModal.jsx web/js/components/preact/users/ApiKeyModal.jsx web/js/components/preact/users/AuditHistoryModal.jsx web/js/components/preact/users/DeleteUserModal.jsx web/js/components/preact/users/EditUserModal.jsx --format json --output-file build/eng-artifacts/T7.164/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.164/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/click-events-have-key-events'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.164`; inspect the raw T1 report and trace the caller. +- **status**: Completed (commit `71dbd4c1`) +- **log**: Ten assigned boundary warnings plus10direct related warnings resolved (lint872→852,0errors/no additions). Direct tests-first10RED; final53/53 combined browser-enabled cases preserve pending guards, focus/drag/confirmation behavior. Types and both43/66asset budgets pass, default restored. Audit apparent Escape failure was a malformed fixture timestamp, not a product defect. +- **files edited/created**: + +### T7.165: jsx-a11y/click-events-have-key-events in RoleManagerModal.jsx, TotpSetupModal.jsx +- **depends_on**: [T7, T10] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/users/RoleManagerModal.jsx`, `web/js/components/preact/users/TotpSetupModal.jsx`, `web/tests/remediation/t7_165.spec.jsx`, `quality/evidence/T7.165.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-9190884804c0c65d, ESLINT-BUCKET-e8ce699e42be07b3 +- **acceptance_criteria**: + - Assert Enter and Space activation where the control semantics require it, focus visibility, no double dispatch, and unchanged pointer interaction. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_165` + - `mkdir -p build/eng-artifacts/T7.165` + - `web/node_modules/.bin/eslint web/js/components/preact/users/RoleManagerModal.jsx web/js/components/preact/users/TotpSetupModal.jsx --format json --output-file build/eng-artifacts/T7.165/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.165/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/click-events-have-key-events'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.165`; inspect the raw T1 report and trace the caller. +- **status**: Completed (commit `fe93b78c`) +- **log**: Four assigned dialog boundary warnings resolved; native keyboard controls and existing dialog behavior preserved. Eight default Node cases and nine explicit Chromium-enabled cases pass. Four directly related interaction warnings also disappear; types, both builds and43/66budgets pass. Lint now887warnings/0errors; broader TOTP modal semantics remain outside scope. +- **files edited/created**: + +### T7.166: jsx-a11y/control-has-associated-label in AuthGate.jsx, BatchDownloadModal.jsx, DetectionOverlay.jsx, FleetView.jsx, FullscreenManager.jsx, HLSVideoCell.jsx +- **depends_on**: [T7, T10, T7.161] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/AuthGate.jsx`, `web/js/components/preact/BatchDownloadModal.jsx`, `web/js/components/preact/DetectionOverlay.jsx`, `web/js/components/preact/FleetView.jsx`, `web/js/components/preact/FullscreenManager.jsx`, `web/js/components/preact/HLSVideoCell.jsx`, `web/tests/remediation/t7_166.spec.jsx`, `quality/evidence/T7.166.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-0f4bbeb4ab715fee, ESLINT-BUCKET-ef00a494fd69875c, ESLINT-BUCKET-e93ca9cb727a0aee, ESLINT-BUCKET-20e1a03b32e40002, ESLINT-BUCKET-e7e0cc4f8fd4c513, ESLINT-BUCKET-aa839344c6e5b30d +- **acceptance_criteria**: + - Render each reported control and assert a stable accessible name, including icon-only actions, without relying on title alone; retain localized labels and original action callbacks. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_166` + - `mkdir -p build/eng-artifacts/T7.166` + - `web/node_modules/.bin/eslint web/js/components/preact/AuthGate.jsx web/js/components/preact/BatchDownloadModal.jsx web/js/components/preact/DetectionOverlay.jsx web/js/components/preact/FleetView.jsx web/js/components/preact/FullscreenManager.jsx web/js/components/preact/HLSVideoCell.jsx --format json --output-file build/eng-artifacts/T7.166/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.166/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/control-has-associated-label'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.166`; inspect the raw T1 report and trace the caller. +- **status**: Completed +- **log**: 2a2e603b resolves11 remaining control-name warnings; original HLS autoplay warning already fixed by d8552365. Named graphic wrapper preserves canvas pixels, dimensions, paused-polling zones and pointer pass-through after recorded direct-role lint failure. Sixteen focused cases and full69suites/744tests pass;22other optional browser supplements unselected. Types, modern43/default66 budgets and freshness pass. Lint665→652with0errors/noadditions. +- **files edited/created**: + +### T7.167: jsx-a11y/control-has-associated-label in LiveView.jsx, LoginView.jsx, MSEVideoCell.jsx, SetupWizard.jsx, SnapshotManager.jsx, StreamConfigModal.jsx +- **depends_on**: [T7, T10, T7.161, T10.2] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/LiveView.jsx`, `web/js/components/preact/LoginView.jsx`, `web/js/components/preact/MSEVideoCell.jsx`, `web/js/components/preact/SetupWizard.jsx`, `web/js/components/preact/SnapshotManager.jsx`, `web/js/components/preact/StreamConfigModal.jsx`, `web/tests/remediation/t7_167.spec.jsx`, `quality/evidence/T7.167.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-ae74d74f4f8f96b4, ESLINT-BUCKET-33c31857a85153c5, ESLINT-BUCKET-2b401ef7608cff74, ESLINT-BUCKET-6b623e0db3f455c7, ESLINT-BUCKET-63e9c68b97a2b704, ESLINT-BUCKET-3d882f111ea61422 +- **acceptance_criteria**: + - Render each reported control and assert a stable accessible name, including icon-only actions, without relying on title alone; retain localized labels and original action callbacks. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_167` + - `mkdir -p build/eng-artifacts/T7.167` + - `web/node_modules/.bin/eslint web/js/components/preact/LiveView.jsx web/js/components/preact/LoginView.jsx web/js/components/preact/MSEVideoCell.jsx web/js/components/preact/SetupWizard.jsx web/js/components/preact/SnapshotManager.jsx web/js/components/preact/StreamConfigModal.jsx --format json --output-file build/eng-artifacts/T7.167/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.167/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/control-has-associated-label'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.167`; inspect the raw T1 report and trace the caller. +- **status**: Completed +- **log**: 01646f56 adds68 names using existing localization without changing callbacks/structure. Sixteen focused cases (eight Chromium),135 related tests, both typechecks and modern43/default66 budgets pass. Full lint652→582, zero errors/no additions; two related StreamConfig label occurrences logged separately. +- **files edited/created**: + +### T7.168: jsx-a11y/control-has-associated-label in StreamsView.jsx, ThemeCustomizer.jsx, UI.jsx, WebRTCVideoCell.jsx, ZoneEditor.jsx, ModalDialog.jsx +- **depends_on**: [T7, T10, T7.161, T7.162, T7.168.2] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/StreamsView.jsx`, `web/js/components/preact/ThemeCustomizer.jsx`, `web/js/components/preact/UI.jsx`, `web/js/components/preact/WebRTCVideoCell.jsx`, `web/js/components/preact/ZoneEditor.jsx`, `web/js/components/preact/common/ModalDialog.jsx`, `web/tests/remediation/t7_168.spec.jsx`, `quality/evidence/T7.168.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-ba71da8483425ebf, ESLINT-BUCKET-6ffa955639ab6418, ESLINT-BUCKET-6faae6bddf434aab, ESLINT-BUCKET-9bd9a8994dceced4, ESLINT-BUCKET-e845a2e465702318, ESLINT-BUCKET-1f35f119bab13f92 +- **acceptance_criteria**: + - Render each reported control and assert a stable accessible name, including icon-only actions, without relying on title alone; retain localized labels and original action callbacks. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_168` + - `mkdir -p build/eng-artifacts/T7.168` + - `web/node_modules/.bin/eslint web/js/components/preact/StreamsView.jsx web/js/components/preact/ThemeCustomizer.jsx web/js/components/preact/UI.jsx web/js/components/preact/WebRTCVideoCell.jsx web/js/components/preact/ZoneEditor.jsx web/js/components/preact/common/ModalDialog.jsx --format json --output-file build/eng-artifacts/T7.168/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.168/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/control-has-associated-label'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.168`; inspect the raw T1 report and trace the caller. +- **status**: Completed (names `88a17566`, behavior `881ee46d`) +- **log**: All 20 remaining names are fixed; the earlier WebRTC name is retained. The separately reproduced first-toggle defect is corrected without replacing its original assertion. Final 38 focused and 83 related browser cases pass, types and both asset profiles pass, default assets restored. Lint remains 440 warnings/0 errors after T7.178; no baseline or exception change. +- **files edited/created**: + +### T7.169: jsx-a11y/control-has-associated-label in EventDestinationEditor.jsx, EventRouteEditor.jsx, BulkOrganizeModal.jsx, CollectionManager.jsx, CollectionSelectorBuilder.jsx, FleetFilters.jsx +- **depends_on**: [T7, T10] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/events/EventDestinationEditor.jsx`, `web/js/components/preact/events/EventRouteEditor.jsx`, `web/js/components/preact/fleet/BulkOrganizeModal.jsx`, `web/js/components/preact/fleet/CollectionManager.jsx`, `web/js/components/preact/fleet/CollectionSelectorBuilder.jsx`, `web/js/components/preact/fleet/FleetFilters.jsx`, `web/tests/remediation/t7_169.spec.jsx`, `quality/evidence/T7.169.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-5088d976b993e707, ESLINT-BUCKET-fea403cece7fffa3, ESLINT-BUCKET-20d0442e85244a5d, ESLINT-BUCKET-03b8b95f3bd44235, ESLINT-BUCKET-993ef9a219cc29a2, ESLINT-BUCKET-eaa9041228587f50 +- **acceptance_criteria**: + - Render each reported control and assert a stable accessible name, including icon-only actions, without relying on title alone; retain localized labels and original action callbacks. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_169` + - `mkdir -p build/eng-artifacts/T7.169` + - `web/node_modules/.bin/eslint web/js/components/preact/events/EventDestinationEditor.jsx web/js/components/preact/events/EventRouteEditor.jsx web/js/components/preact/fleet/BulkOrganizeModal.jsx web/js/components/preact/fleet/CollectionManager.jsx web/js/components/preact/fleet/CollectionSelectorBuilder.jsx web/js/components/preact/fleet/FleetFilters.jsx --format json --output-file build/eng-artifacts/T7.169/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.169/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/control-has-associated-label'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.169`; inspect the raw T1 report and trace the caller. +- **status**: Completed +- **log**: 836ac0a2 adds45 localized accessible control names without changing handlers/structure. Six suites51/51 pass including19focused/7Chromium; types and modern43/default66 budgets/freshness pass. Full lint742→690with0errors,45assigned+7related removals. +- **files edited/created**: + +### T7.170: jsx-a11y/control-has-associated-label in OrganizationManager.jsx, SavedViewsBar.jsx, StaticCollectionMembers.jsx, InvestigationActions.jsx, InvestigationBookmarks.jsx, InvestigationView.jsx +- **depends_on**: [T7, T10] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/fleet/OrganizationManager.jsx`, `web/js/components/preact/fleet/SavedViewsBar.jsx`, `web/js/components/preact/fleet/StaticCollectionMembers.jsx`, `web/js/components/preact/investigation/InvestigationActions.jsx`, `web/js/components/preact/investigation/InvestigationBookmarks.jsx`, `web/js/components/preact/investigation/InvestigationView.jsx`, `web/tests/remediation/t7_170.spec.jsx`, `quality/evidence/T7.170.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-19d5fc636e3f15e6, ESLINT-BUCKET-2cdd7ef73529551e, ESLINT-BUCKET-5a25b9ace393f230, ESLINT-BUCKET-12c7bb31966e53aa, ESLINT-BUCKET-274240d1fc0300f7, ESLINT-BUCKET-2a20bf23c61a102e +- **acceptance_criteria**: + - Render each reported control and assert a stable accessible name, including icon-only actions, without relying on title alone; retain localized labels and original action callbacks. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_170` + - `mkdir -p build/eng-artifacts/T7.170` + - `web/node_modules/.bin/eslint web/js/components/preact/fleet/OrganizationManager.jsx web/js/components/preact/fleet/SavedViewsBar.jsx web/js/components/preact/fleet/StaticCollectionMembers.jsx web/js/components/preact/investigation/InvestigationActions.jsx web/js/components/preact/investigation/InvestigationBookmarks.jsx web/js/components/preact/investigation/InvestigationView.jsx --format json --output-file build/eng-artifacts/T7.170/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.170/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/control-has-associated-label'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.170`; inspect the raw T1 report and trace the caller. +- **status**: Completed +- **log**: e29ab093 resolves24 control-name warnings with existing localized names/camera labels, retaining callbacks and page-owned media controls. Eight suites85/85pass including15focused/7Chromium; types and modern43/default66 budgets/freshness pass. Lint690→665with0errors; one related label occurrence logged separately. +- **files edited/created**: + +### T7.171: jsx-a11y/control-has-associated-label in LiveBuildingPlan.jsx, LiveOperatorNavigator.jsx, PlanSketchControls.jsx, FiltersSidebar.jsx, RecordingsGrid.jsx, RecordingsTable.jsx +- **depends_on**: [T7, T10, T7.163, T10.3] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/live/LiveBuildingPlan.jsx`, `web/js/components/preact/live/LiveOperatorNavigator.jsx`, `web/js/components/preact/live/PlanSketchControls.jsx`, `web/js/components/preact/recordings/FiltersSidebar.jsx`, `web/js/components/preact/recordings/RecordingsGrid.jsx`, `web/js/components/preact/recordings/RecordingsTable.jsx`, `web/tests/remediation/t7_171.spec.jsx`, `quality/evidence/T7.171.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-bb1a968a245405ec, ESLINT-BUCKET-3a6fb0a94ef6ffdf, ESLINT-BUCKET-e0e4b387af698b40, ESLINT-BUCKET-389226ea0147ea5d, ESLINT-BUCKET-aab67d5bbb7b5489, ESLINT-BUCKET-f492731b13ec277c +- **acceptance_criteria**: + - Render each reported control and assert a stable accessible name, including icon-only actions, without relying on title alone; retain localized labels and original action callbacks. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_171` + - `mkdir -p build/eng-artifacts/T7.171` + - `web/node_modules/.bin/eslint web/js/components/preact/live/LiveBuildingPlan.jsx web/js/components/preact/live/LiveOperatorNavigator.jsx web/js/components/preact/live/PlanSketchControls.jsx web/js/components/preact/recordings/FiltersSidebar.jsx web/js/components/preact/recordings/RecordingsGrid.jsx web/js/components/preact/recordings/RecordingsTable.jsx --format json --output-file build/eng-artifacts/T7.171/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.171/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/control-has-associated-label'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.171`; inspect the raw T1 report and trace the caller. +- **status**: Completed (commit `efeca7c9`) +- **log**: All29assigned names resolved using existing translations; lint806→777with0errors/nootherdelta.14focused (5Chromium),65related cases, types, modern43/default66assets/freshness pass; default restored. +- **files edited/created**: + +### T7.172: jsx-a11y/control-has-associated-label in TagsOverlay.jsx, AdvancedTab.jsx, AuthTab.jsx, DetectionTab.jsx, GeneralTab.jsx, Go2rtcTab.jsx +- **depends_on**: [T7, T10, T7.163, T10.2, T10.3, T7.172.1] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/recordings/TagsOverlay.jsx`, `web/js/components/preact/settings/AdvancedTab.jsx`, `web/js/components/preact/settings/AuthTab.jsx`, `web/js/components/preact/settings/DetectionTab.jsx`, `web/js/components/preact/settings/GeneralTab.jsx`, `web/js/components/preact/settings/Go2rtcTab.jsx`, `web/tests/remediation/t7_172.spec.jsx`, `quality/evidence/T7.172.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-d03afa69ead3203e, ESLINT-BUCKET-8ce22b0413a485e1, ESLINT-BUCKET-40ef7b2f328bb9ef, ESLINT-BUCKET-93d2b8db97a42555, ESLINT-BUCKET-ea86635ccd79513e, ESLINT-BUCKET-5ec0649750441f81 +- **acceptance_criteria**: + - Render each reported control and assert a stable accessible name, including icon-only actions, without relying on title alone; retain localized labels and original action callbacks. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_172` + - `mkdir -p build/eng-artifacts/T7.172` + - `web/node_modules/.bin/eslint web/js/components/preact/recordings/TagsOverlay.jsx web/js/components/preact/settings/AdvancedTab.jsx web/js/components/preact/settings/AuthTab.jsx web/js/components/preact/settings/DetectionTab.jsx web/js/components/preact/settings/GeneralTab.jsx web/js/components/preact/settings/Go2rtcTab.jsx --format json --output-file build/eng-artifacts/T7.172/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.172/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/control-has-associated-label'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.172`; inspect the raw T1 report and trace the caller. +- **status**: Completed (names `661cfb61`, behavior `9c355497`) +- **log**: All 50 assigned accessible names covered; separately scoped bulk-tag defect fixed and original delayed-props assertion actively green. Final 22 focused/143 related pass, both type checks and asset profiles pass, lint stays 532 warnings/0 errors with unchanged rule/file counts after behavior fix. Default assets restored. +- **files edited/created**: + +### T7.173: jsx-a11y/control-has-associated-label in HealthTab.jsx, MqttTab.jsx, StorageTab.jsx, StreamsDefaultsTab.jsx, StreamStorage.jsx, CalendarPicker.jsx +- **depends_on**: [T7, T10, T10.2] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/settings/HealthTab.jsx`, `web/js/components/preact/settings/MqttTab.jsx`, `web/js/components/preact/settings/StorageTab.jsx`, `web/js/components/preact/settings/StreamsDefaultsTab.jsx`, `web/js/components/preact/system/StreamStorage.jsx`, `web/js/components/preact/timeline/CalendarPicker.jsx`, `web/tests/remediation/t7_173.spec.jsx`, `quality/evidence/T7.173.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-fe8c1e237393fb23, ESLINT-BUCKET-59e343114dc251b1, ESLINT-BUCKET-5a1cbbfddf8f3fcc, ESLINT-BUCKET-350da16b1c3b2274, ESLINT-BUCKET-44b516dc2eeb97bf, ESLINT-BUCKET-5da42ce096a9fa82 +- **acceptance_criteria**: + - Render each reported control and assert a stable accessible name, including icon-only actions, without relying on title alone; retain localized labels and original action callbacks. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_173` + - `mkdir -p build/eng-artifacts/T7.173` + - `web/node_modules/.bin/eslint web/js/components/preact/settings/HealthTab.jsx web/js/components/preact/settings/MqttTab.jsx web/js/components/preact/settings/StorageTab.jsx web/js/components/preact/settings/StreamsDefaultsTab.jsx web/js/components/preact/system/StreamStorage.jsx web/js/components/preact/timeline/CalendarPicker.jsx --format json --output-file build/eng-artifacts/T7.173/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.173/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/control-has-associated-label'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.173`; inspect the raw T1 report and trace the caller. +- **status**: Completed (fix `dc3199fb`) +- **log**: All68assigned naming diagnostics resolved with18focused cases (9Chromium),108related cases, both types and43modern/66defaultbudgets passing; default fresh. Lint532→461warnings/0errors with no increased buckets. Coordinator AST comparison confirms only aria-label changes across all6production files. Related T7.178 reductions recorded separately. +- **files edited/created**: + +### T7.174: jsx-a11y/control-has-associated-label in TimelineControls.jsx, TimelinePlayer.jsx, AddUserModal.jsx, ApiKeyModal.jsx, AuditHistoryModal.jsx, EditUserModal.jsx +- **depends_on**: [T7, T10, T7.163, T7.164] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/timeline/TimelineControls.jsx`, `web/js/components/preact/timeline/TimelinePlayer.jsx`, `web/js/components/preact/users/AddUserModal.jsx`, `web/js/components/preact/users/ApiKeyModal.jsx`, `web/js/components/preact/users/AuditHistoryModal.jsx`, `web/js/components/preact/users/EditUserModal.jsx`, `web/tests/remediation/t7_174.spec.jsx`, `quality/evidence/T7.174.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-7b00d558899dd292, ESLINT-BUCKET-4e38c49052b49016, ESLINT-BUCKET-6af902622576c683, ESLINT-BUCKET-098293b0fb5320bb, ESLINT-BUCKET-f25f197e02c782fc, ESLINT-BUCKET-ccf3e6e7959d2bd7 +- **acceptance_criteria**: + - Render each reported control and assert a stable accessible name, including icon-only actions, without relying on title alone; retain localized labels and original action callbacks. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_174` + - `mkdir -p build/eng-artifacts/T7.174` + - `web/node_modules/.bin/eslint web/js/components/preact/timeline/TimelineControls.jsx web/js/components/preact/timeline/TimelinePlayer.jsx web/js/components/preact/users/AddUserModal.jsx web/js/components/preact/users/ApiKeyModal.jsx web/js/components/preact/users/AuditHistoryModal.jsx web/js/components/preact/users/EditUserModal.jsx --format json --output-file build/eng-artifacts/T7.174/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.174/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/control-has-associated-label'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.174`; inspect the raw T1 report and trace the caller. +- **status**: Completed (commit `d5aa9244`) +- **log**: All27assigned control-name warnings plus1related API label occurrence removed; existing translations and callbacks preserved.62/62combined browser-enabled checks, post-default12/12, types, modern43/default66asset checks pass. No baseline/locale changes. +- **files edited/created**: + +### T7.175: jsx-a11y/control-has-associated-label in RoleManagerModal.jsx, TotpSetupModal.jsx, UsersTable.jsx +- **depends_on**: [T7, T10, T7.165] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/users/RoleManagerModal.jsx`, `web/js/components/preact/users/TotpSetupModal.jsx`, `web/js/components/preact/users/UsersTable.jsx`, `web/tests/remediation/t7_175.spec.jsx`, `web/tests/remediation/t7_165.spec.jsx` (useId hook mock compatibility only; preserve assertions), `quality/evidence/T7.175.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-400e44e71536be4e, ESLINT-BUCKET-d3aede71a08c3a5f, ESLINT-BUCKET-fcf1de019f6b4533 +- **acceptance_criteria**: + - Render each reported control and assert a stable accessible name, including icon-only actions, without relying on title alone; retain localized labels and original action callbacks. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_175` + - `mkdir -p build/eng-artifacts/T7.175` + - `web/node_modules/.bin/eslint web/js/components/preact/users/RoleManagerModal.jsx web/js/components/preact/users/TotpSetupModal.jsx web/js/components/preact/users/UsersTable.jsx --format json --output-file build/eng-artifacts/T7.175/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.175/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/control-has-associated-label'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.175`; inspect the raw T1 report and trace the caller. +- **status**: Completed (commit `359ade6d`) +- **log**: All11 mapped control-name warnings resolved using existing localized visible text/button titles;14/14 combined browser-enabled tests preserve modal boundaries and callbacks. Full lint887→873 with0errors/no additions (3 related RoleManager label-for warnings also clear); types and both43/66asset budgets pass, default restored. +- **files edited/created**: + +### T7.176: jsx-a11y/interactive-supports-focus in MobileTileGestures.jsx +- **depends_on**: [T7, T10] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/MobileTileGestures.jsx`, `web/tests/remediation/t7_176.spec.jsx`, `quality/evidence/T7.176.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-3469a0a703cdbc12 +- **acceptance_criteria**: + - Assert each interactive control is keyboard focusable and acts once for its expected activation keys. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_176` + - `mkdir -p build/eng-artifacts/T7.176` + - `web/node_modules/.bin/eslint web/js/components/preact/MobileTileGestures.jsx --format json --output-file build/eng-artifacts/T7.176/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.176/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/interactive-supports-focus'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.176`; inspect the raw T1 report and trace the caller. +- **status**: Completed (commit `29b55fd6`) +- **log**: Composite menu receives tabIndex=-1 while keeping first-native-child focus/activation. Seven explicit tests including Chromium focus/Escape/Tab/Enter/Space pass (six default Node cases). Exactly one mapped warning removed; lint now895warnings/0errors, types/builds/budgets pass. No full APG conformance claim. +- **files edited/created**: + +### T7.177: jsx-a11y/label-has-associated-control in BatchDownloadModal.jsx, StreamConfigModal.jsx, EventDestinationEditor.jsx, EventRouteEditor.jsx, CollectionManager.jsx, StaticCollectionMembers.jsx +- **depends_on**: [T7, T10, T7.166, T7.167, T7.169, T7.170, T10.2] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/BatchDownloadModal.jsx`, `web/js/components/preact/StreamConfigModal.jsx`, `web/js/components/preact/events/EventDestinationEditor.jsx`, `web/js/components/preact/events/EventRouteEditor.jsx`, `web/js/components/preact/fleet/CollectionManager.jsx`, `web/js/components/preact/fleet/StaticCollectionMembers.jsx`, `web/tests/remediation/t7_177.spec.jsx`, `web/tests/remediation/t7_167.spec.jsx` field locator compatibility only, `web/tests/e2e/pages/StreamsPage.js` name/url/enabled/record field selectors only, `quality/evidence/T7.177.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-199d3d91208953b4, ESLINT-BUCKET-81a756fe7228ad5b, ESLINT-BUCKET-ddc6803a9b16e391, ESLINT-BUCKET-7cde28afaa9ffb65, ESLINT-BUCKET-7dcecd444f491721, ESLINT-BUCKET-9308fe6583bcdce7 +- **acceptance_criteria**: + - Assert label/input association is programmatic, unique in repeated forms, localized and preserved during conditional rendering. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_177` + - `mkdir -p build/eng-artifacts/T7.177` + - `web/node_modules/.bin/eslint web/js/components/preact/BatchDownloadModal.jsx web/js/components/preact/StreamConfigModal.jsx web/js/components/preact/events/EventDestinationEditor.jsx web/js/components/preact/events/EventRouteEditor.jsx web/js/components/preact/fleet/CollectionManager.jsx web/js/components/preact/fleet/StaticCollectionMembers.jsx --format json --output-file build/eng-artifacts/T7.177/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.177/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/label-has-associated-control'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.177`; inspect the raw T1 report and trace the caller. +- **status**: Completed (fix `8df5a224`) +- **log**: Commit 8df5a224 preserves all six already-resolved lint buckets and fixes remaining repeated-editor field/selector IDs, ZIP filename association and radio grouping. Actual 10 failing/4 passing controls become 14/14 focused and 80/80 related browser cases passing. Default Jest 750 pass/115 optional skips; types and modern 43/default 66 asset budgets pass, default restored. Lint 440→438 warnings/0 errors with no increased buckets; unrelated dialog/form/action IDs remain outside scope. +- **files edited/created**: + +### T7.178: jsx-a11y/label-has-associated-control in HealthTab.jsx, StorageTab.jsx, ApiKeyModal.jsx +- **depends_on**: [T7, T10, T7.173, T7.174, T10.2] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/settings/HealthTab.jsx`, `web/js/components/preact/settings/StorageTab.jsx`, `web/js/components/preact/users/ApiKeyModal.jsx`, `web/tests/remediation/t7_178.spec.jsx`, `web/tests/remediation/t7_173.spec.jsx` and `web/tests/remediation/t7_174.spec.jsx` hook/mock and locator compatibility only (T7.174 may also wait for the existing zoom-disabled state before its unchanged assertion), `web/tests/remediation/t7_164.spec.jsx` useId hook mock only, `quality/evidence/T7.178.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-5e7cfcb56208eaa9, ESLINT-BUCKET-e84d458d8fcd3eef, ESLINT-BUCKET-cf4b93673820960b +- **acceptance_criteria**: + - Assert label/input association is programmatic, unique in repeated forms, localized and preserved during conditional rendering. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_178` + - `mkdir -p build/eng-artifacts/T7.178` + - `web/node_modules/.bin/eslint web/js/components/preact/settings/HealthTab.jsx web/js/components/preact/settings/StorageTab.jsx web/js/components/preact/users/ApiKeyModal.jsx --format json --output-file build/eng-artifacts/T7.178/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.178/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/label-has-associated-control'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.178`; inspect the raw T1 report and trace the caller. +- **status**: Completed (fix `18a881ab`) +- **log**: Commit18a881ab verifies all original buckets remain zero and fixes remaining repeated-form associations: 14Storage ID pairs, both conditional selector prefixes, and orphan group heading. Actual7/9RED becomes75/75related browser cases passing; default743pass/102optional skips, types, modern43/default66budgets pass and default restored. Lint441→440warnings/0errors, no increases. Separate Zone Editor browser failure remains open. +- **files edited/created**: + +### T7.179: jsx-a11y/label-has-for in AuthGate.jsx, BatchDownloadModal.jsx, FleetView.jsx, LiveView.jsx, LoginView.jsx, SetupWizard.jsx +- **depends_on**: [T7, T10, T7.166, T7.177, T7.167] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/AuthGate.jsx`, `web/js/components/preact/BatchDownloadModal.jsx`, `web/js/components/preact/FleetView.jsx`, `web/js/components/preact/LiveView.jsx`, `web/js/components/preact/LoginView.jsx`, `web/js/components/preact/SetupWizard.jsx`, `web/tests/remediation/t7_179.spec.jsx`, `quality/evidence/T7.179.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-00c9eb79387b90d1, ESLINT-BUCKET-c603f4c09210c0bd, ESLINT-BUCKET-07e48ad6ea9ce45e, ESLINT-BUCKET-dfeaa6c4a9abf5fa, ESLINT-BUCKET-98f2a6813a7ad225, ESLINT-BUCKET-e73c0530b2243573 +- **acceptance_criteria**: + - Assert each label resolves to its intended input by id/for with unique IDs across repeated components and that clicking it focuses the input. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_179` + - `mkdir -p build/eng-artifacts/T7.179` + - `web/node_modules/.bin/eslint web/js/components/preact/AuthGate.jsx web/js/components/preact/BatchDownloadModal.jsx web/js/components/preact/FleetView.jsx web/js/components/preact/LiveView.jsx web/js/components/preact/LoginView.jsx web/js/components/preact/SetupWizard.jsx --format json --output-file build/eng-artifacts/T7.179/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.179/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/label-has-for'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.179`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.180: jsx-a11y/label-has-for in StreamCard.jsx, StreamConfigModal.jsx, StreamsView.jsx, UI.jsx, ZoneEditor.jsx, ModalDialog.jsx +- **depends_on**: [T7, T10, T7.161, T7.177, T7.168, T10.2] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/StreamCard.jsx`, `web/js/components/preact/StreamConfigModal.jsx`, `web/js/components/preact/StreamsView.jsx`, `web/js/components/preact/UI.jsx`, `web/js/components/preact/ZoneEditor.jsx`, `web/js/components/preact/common/ModalDialog.jsx`, `web/tests/remediation/t7_180.spec.jsx`, `quality/evidence/T7.180.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-c4c40aa44f4e62e4, ESLINT-BUCKET-d900a116e1f06cf4, ESLINT-BUCKET-de63541e57266afb, ESLINT-BUCKET-e810ef87137eb5d8, ESLINT-BUCKET-38ac9714530327f7, ESLINT-BUCKET-b1a74f710eb47a22 +- **acceptance_criteria**: + - Assert each label resolves to its intended input by id/for with unique IDs across repeated components and that clicking it focuses the input. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_180` + - `mkdir -p build/eng-artifacts/T7.180` + - `web/node_modules/.bin/eslint web/js/components/preact/StreamCard.jsx web/js/components/preact/StreamConfigModal.jsx web/js/components/preact/StreamsView.jsx web/js/components/preact/UI.jsx web/js/components/preact/ZoneEditor.jsx web/js/components/preact/common/ModalDialog.jsx --format json --output-file build/eng-artifacts/T7.180/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.180/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/label-has-for'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.180`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.181: jsx-a11y/label-has-for in EventDestinationEditor.jsx, EventRouteEditor.jsx, BulkOrganizeModal.jsx, CollectionManager.jsx, CollectionSelectorBuilder.jsx, FleetTable.jsx +- **depends_on**: [T7, T10, T7.177, T7.169] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/events/EventDestinationEditor.jsx`, `web/js/components/preact/events/EventRouteEditor.jsx`, `web/js/components/preact/fleet/BulkOrganizeModal.jsx`, `web/js/components/preact/fleet/CollectionManager.jsx`, `web/js/components/preact/fleet/CollectionSelectorBuilder.jsx`, `web/js/components/preact/fleet/FleetTable.jsx`, `web/tests/remediation/t7_181.spec.jsx`, `quality/evidence/T7.181.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-2d6e882afea8fed1, ESLINT-BUCKET-f3bd5129ab03f352, ESLINT-BUCKET-7ba2e66a4eb3914f, ESLINT-BUCKET-05ad31a80cc2b3a2, ESLINT-BUCKET-30a2e484727d4a5a, ESLINT-BUCKET-b32370126e568d01 +- **acceptance_criteria**: + - Assert each label resolves to its intended input by id/for with unique IDs across repeated components and that clicking it focuses the input. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_181` + - `mkdir -p build/eng-artifacts/T7.181` + - `web/node_modules/.bin/eslint web/js/components/preact/events/EventDestinationEditor.jsx web/js/components/preact/events/EventRouteEditor.jsx web/js/components/preact/fleet/BulkOrganizeModal.jsx web/js/components/preact/fleet/CollectionManager.jsx web/js/components/preact/fleet/CollectionSelectorBuilder.jsx web/js/components/preact/fleet/FleetTable.jsx --format json --output-file build/eng-artifacts/T7.181/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.181/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/label-has-for'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.181`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.182: jsx-a11y/label-has-for in OrganizationManager.jsx, SavedViewsBar.jsx, StaticCollectionMembers.jsx, InvestigationActions.jsx, InvestigationBookmarks.jsx, InvestigationView.jsx +- **depends_on**: [T7, T10, T7.170, T7.177] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/fleet/OrganizationManager.jsx`, `web/js/components/preact/fleet/SavedViewsBar.jsx`, `web/js/components/preact/fleet/StaticCollectionMembers.jsx`, `web/js/components/preact/investigation/InvestigationActions.jsx`, `web/js/components/preact/investigation/InvestigationBookmarks.jsx`, `web/js/components/preact/investigation/InvestigationView.jsx`, `web/tests/remediation/t7_182.spec.jsx`, `quality/evidence/T7.182.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-f77074e66ed65721, ESLINT-BUCKET-e26c8068ece9c0e0, ESLINT-BUCKET-4246d4277a0b8f04, ESLINT-BUCKET-642b5326a1034302, ESLINT-BUCKET-c0802f3ddbb20b2e, ESLINT-BUCKET-137cbeae12c87193 +- **acceptance_criteria**: + - Assert each label resolves to its intended input by id/for with unique IDs across repeated components and that clicking it focuses the input. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_182` + - `mkdir -p build/eng-artifacts/T7.182` + - `web/node_modules/.bin/eslint web/js/components/preact/fleet/OrganizationManager.jsx web/js/components/preact/fleet/SavedViewsBar.jsx web/js/components/preact/fleet/StaticCollectionMembers.jsx web/js/components/preact/investigation/InvestigationActions.jsx web/js/components/preact/investigation/InvestigationBookmarks.jsx web/js/components/preact/investigation/InvestigationView.jsx --format json --output-file build/eng-artifacts/T7.182/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.182/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/label-has-for'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.182`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.183: jsx-a11y/label-has-for in LiveBuildingPlan.jsx, LiveOperatorNavigator.jsx, FiltersSidebar.jsx, RecordingsGrid.jsx, RecordingsTable.jsx, TagsOverlay.jsx +- **depends_on**: [T7, T10, T7.171, T7.172, T10.3] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/live/LiveBuildingPlan.jsx`, `web/js/components/preact/live/LiveOperatorNavigator.jsx`, `web/js/components/preact/recordings/FiltersSidebar.jsx`, `web/js/components/preact/recordings/RecordingsGrid.jsx`, `web/js/components/preact/recordings/RecordingsTable.jsx`, `web/js/components/preact/recordings/TagsOverlay.jsx`, `web/tests/remediation/t7_183.spec.jsx`, `quality/evidence/T7.183.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-a167dfb879b1b9cf, ESLINT-BUCKET-b0e1dbc8e8cc536e, ESLINT-BUCKET-50994f387baeaa69, ESLINT-BUCKET-dcde47440ba091e3, ESLINT-BUCKET-114c375fcc3b4802, ESLINT-BUCKET-8b431e9fa35d81ba +- **acceptance_criteria**: + - Assert each label resolves to its intended input by id/for with unique IDs across repeated components and that clicking it focuses the input. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_183` + - `mkdir -p build/eng-artifacts/T7.183` + - `web/node_modules/.bin/eslint web/js/components/preact/live/LiveBuildingPlan.jsx web/js/components/preact/live/LiveOperatorNavigator.jsx web/js/components/preact/recordings/FiltersSidebar.jsx web/js/components/preact/recordings/RecordingsGrid.jsx web/js/components/preact/recordings/RecordingsTable.jsx web/js/components/preact/recordings/TagsOverlay.jsx --format json --output-file build/eng-artifacts/T7.183/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.183/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/label-has-for'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.183`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.184: jsx-a11y/label-has-for in AdvancedTab.jsx, AuthTab.jsx, DetectionTab.jsx, GeneralTab.jsx, Go2rtcTab.jsx, HealthTab.jsx +- **depends_on**: [T7, T10, T7.172, T7.178, T10.2] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/settings/AdvancedTab.jsx`, `web/js/components/preact/settings/AuthTab.jsx`, `web/js/components/preact/settings/DetectionTab.jsx`, `web/js/components/preact/settings/GeneralTab.jsx`, `web/js/components/preact/settings/Go2rtcTab.jsx`, `web/js/components/preact/settings/HealthTab.jsx`, `web/tests/remediation/t7_184.spec.jsx`, `quality/evidence/T7.184.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-a4e1217d107c230c, ESLINT-BUCKET-4a0d0e1ea3635a24, ESLINT-BUCKET-d6ed69efe339ce37, ESLINT-BUCKET-61d1473d84fbeea6, ESLINT-BUCKET-dd680edebf6809ff, ESLINT-BUCKET-e267f8f47c3806d3 +- **acceptance_criteria**: + - Assert each label resolves to its intended input by id/for with unique IDs across repeated components and that clicking it focuses the input. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_184` + - `mkdir -p build/eng-artifacts/T7.184` + - `web/node_modules/.bin/eslint web/js/components/preact/settings/AdvancedTab.jsx web/js/components/preact/settings/AuthTab.jsx web/js/components/preact/settings/DetectionTab.jsx web/js/components/preact/settings/GeneralTab.jsx web/js/components/preact/settings/Go2rtcTab.jsx web/js/components/preact/settings/HealthTab.jsx --format json --output-file build/eng-artifacts/T7.184/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.184/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/label-has-for'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.184`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.185: jsx-a11y/label-has-for in MqttTab.jsx, StorageTab.jsx, StreamsDefaultsTab.jsx, WorkspacesTab.jsx, TimelinePage.jsx, TimelinePlayer.jsx +- **depends_on**: [T7, T10, T7.173, T7.178, T7.174, T10.2] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/settings/MqttTab.jsx`, `web/js/components/preact/settings/StorageTab.jsx`, `web/js/components/preact/settings/StreamsDefaultsTab.jsx`, `web/js/components/preact/settings/WorkspacesTab.jsx`, `web/js/components/preact/timeline/TimelinePage.jsx`, `web/js/components/preact/timeline/TimelinePlayer.jsx`, `web/tests/remediation/t7_185.spec.jsx`, `quality/evidence/T7.185.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-d5ecbc0292c55216, ESLINT-BUCKET-c067bb3093f57d49, ESLINT-BUCKET-4e8b56001c3a0722, ESLINT-BUCKET-223399111dfd4016, ESLINT-BUCKET-7d2f50f4d066f6e4, ESLINT-BUCKET-00d366c5691b4bb5 +- **acceptance_criteria**: + - Assert each label resolves to its intended input by id/for with unique IDs across repeated components and that clicking it focuses the input. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_185` + - `mkdir -p build/eng-artifacts/T7.185` + - `web/node_modules/.bin/eslint web/js/components/preact/settings/MqttTab.jsx web/js/components/preact/settings/StorageTab.jsx web/js/components/preact/settings/StreamsDefaultsTab.jsx web/js/components/preact/settings/WorkspacesTab.jsx web/js/components/preact/timeline/TimelinePage.jsx web/js/components/preact/timeline/TimelinePlayer.jsx --format json --output-file build/eng-artifacts/T7.185/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.185/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/label-has-for'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.185`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: T7.178 removes one StorageTab orphan group-heading label-has-for warning; whole task remains open. +- **files edited/created**: + +### T7.186: jsx-a11y/label-has-for in AccessPolicyModal.jsx, AddUserModal.jsx, ApiKeyModal.jsx, AuditDecisionModes.jsx, AuditHistoryModal.jsx, EditUserModal.jsx +- **depends_on**: [T7, T10, T7.164, T7.174, T7.178] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/users/AccessPolicyModal.jsx`, `web/js/components/preact/users/AddUserModal.jsx`, `web/js/components/preact/users/ApiKeyModal.jsx`, `web/js/components/preact/users/AuditDecisionModes.jsx`, `web/js/components/preact/users/AuditHistoryModal.jsx`, `web/js/components/preact/users/EditUserModal.jsx`, `web/tests/remediation/t7_186.spec.jsx`, `quality/evidence/T7.186.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-7f43090b7b01626a, ESLINT-BUCKET-17ad0e14291e8ec3, ESLINT-BUCKET-18b7f3add93999e4, ESLINT-BUCKET-d3157833e1568912, ESLINT-BUCKET-184f5809a1f3d621, ESLINT-BUCKET-682efc8151a90daf +- **acceptance_criteria**: + - Assert each label resolves to its intended input by id/for with unique IDs across repeated components and that clicking it focuses the input. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_186` + - `mkdir -p build/eng-artifacts/T7.186` + - `web/node_modules/.bin/eslint web/js/components/preact/users/AccessPolicyModal.jsx web/js/components/preact/users/AddUserModal.jsx web/js/components/preact/users/ApiKeyModal.jsx web/js/components/preact/users/AuditDecisionModes.jsx web/js/components/preact/users/AuditHistoryModal.jsx web/js/components/preact/users/EditUserModal.jsx --format json --output-file build/eng-artifacts/T7.186/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.186/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/label-has-for'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.186`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.187: jsx-a11y/label-has-for in RoleManagerModal.jsx, TotpSetupModal.jsx +- **depends_on**: [T7, T10, T7.175] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/users/RoleManagerModal.jsx`, `web/js/components/preact/users/TotpSetupModal.jsx`, `web/tests/remediation/t7_187.spec.jsx`, `web/tests/remediation/t7_165.spec.jsx` (fixed-heading selector compatibility only; preserve saving-guard assertion), `quality/evidence/T7.187.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-2e14d60ae388c180, ESLINT-BUCKET-9a54a74c58d29d4c +- **acceptance_criteria**: + - Assert each label resolves to its intended input by id/for with unique IDs across repeated components and that clicking it focuses the input. Repeated RoleManager dialogs must also resolve their own uniquely identified heading; reuse the existing per-instance ID. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_187` + - `mkdir -p build/eng-artifacts/T7.187` + - `web/node_modules/.bin/eslint web/js/components/preact/users/RoleManagerModal.jsx web/js/components/preact/users/TotpSetupModal.jsx --format json --output-file build/eng-artifacts/T7.187/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.187/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/label-has-for'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.187`; inspect the raw T1 report and trace the caller. +- **status**: Completed (implementation `89474721`, full-suite evidence `a02a3cac`) +- **log**: Tests-first missing TOTP nesting and duplicate repeated dialog-heading IDs reproduced;17/17 combined browser-enabled cases pass. Exact lint873→872 with0errors/no new warnings; types and both43/66asset budgets pass. Full web suite60/60,598passed,5opt-in browser tests unselected; browser supplement executed separately. Default dist restored. +- **files edited/created**: + +### T7.188: jsx-a11y/media-has-caption in HLSVideoCell.jsx, MSEVideoCell.jsx, UI.jsx, WebRTCVideoCell.jsx, InvestigationView.jsx, TimelinePlayer.jsx +- **depends_on**: [T7, T10, T7.166, T7.167, T7.180, T7.168, T7.182, T7.185] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/HLSVideoCell.jsx`, `web/js/components/preact/MSEVideoCell.jsx`, `web/js/components/preact/UI.jsx`, `web/js/components/preact/WebRTCVideoCell.jsx`, `web/js/components/preact/investigation/InvestigationView.jsx`, `web/js/components/preact/timeline/TimelinePlayer.jsx`, `web/tests/remediation/t7_188.spec.jsx`, `quality/evidence/T7.188.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-165a348ebdcecd71, ESLINT-BUCKET-1760eab07e0cce49, ESLINT-BUCKET-df0c5f209b6ef6ab, ESLINT-BUCKET-c464a6c1e23bf196, ESLINT-BUCKET-de50a4c99ae7d421, ESLINT-BUCKET-7189b497b5a855df +- **acceptance_criteria**: + - Identify prerecorded speech versus live/silent surveillance streams; provide applicable caption support, or an explicitly reviewed narrow semantic exception with evidence. Never add a fake empty caption track. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_188` + - `mkdir -p build/eng-artifacts/T7.188` + - `web/node_modules/.bin/eslint web/js/components/preact/HLSVideoCell.jsx web/js/components/preact/MSEVideoCell.jsx web/js/components/preact/UI.jsx web/js/components/preact/WebRTCVideoCell.jsx web/js/components/preact/investigation/InvestigationView.jsx web/js/components/preact/timeline/TimelinePlayer.jsx --format json --output-file build/eng-artifacts/T7.188/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.188/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/media-has-caption'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.188`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.189: jsx-a11y/mouse-events-have-key-events in DayNightControl.jsx, HLSVideoCell.jsx, SnapshotManager.jsx, StreamCard.jsx, StreamsView.jsx, Toast.jsx +- **depends_on**: [T7, T10, T7.188, T7.167, T7.180] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/DayNightControl.jsx`, `web/js/components/preact/HLSVideoCell.jsx`, `web/js/components/preact/SnapshotManager.jsx`, `web/js/components/preact/StreamCard.jsx`, `web/js/components/preact/StreamsView.jsx`, `web/js/components/preact/Toast.jsx`, `web/tests/remediation/t7_189.spec.jsx`, `quality/evidence/T7.189.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-bc4e9a8b10d83163, ESLINT-BUCKET-c31a4d9816cac7ce, ESLINT-BUCKET-e715a00f3dbe2f4f, ESLINT-BUCKET-9a70a1ccf37b9405, ESLINT-BUCKET-e876245abbef0a79, ESLINT-BUCKET-c811624672d6bfff +- **acceptance_criteria**: + - Assert focus/blur and mouse enter/leave expose and dismiss the same content without trapping focus. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_189` + - `mkdir -p build/eng-artifacts/T7.189` + - `web/node_modules/.bin/eslint web/js/components/preact/DayNightControl.jsx web/js/components/preact/HLSVideoCell.jsx web/js/components/preact/SnapshotManager.jsx web/js/components/preact/StreamCard.jsx web/js/components/preact/StreamsView.jsx web/js/components/preact/Toast.jsx --format json --output-file build/eng-artifacts/T7.189/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.189/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/mouse-events-have-key-events'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.189`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.190: jsx-a11y/mouse-events-have-key-events in WebRTCVideoCell.jsx, RecordingsTable.jsx, TimelinePage.jsx +- **depends_on**: [T7, T10, T7.188, T7.183, T7.185, T10.3] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/WebRTCVideoCell.jsx`, `web/js/components/preact/recordings/RecordingsTable.jsx`, `web/js/components/preact/timeline/TimelinePage.jsx`, `web/tests/remediation/t7_190.spec.jsx`, `quality/evidence/T7.190.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-a4f59c579d19ce1e, ESLINT-BUCKET-7a540f91903a1e57, ESLINT-BUCKET-dcdb657c2038e332 +- **acceptance_criteria**: + - Assert focus/blur and mouse enter/leave expose and dismiss the same content without trapping focus. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_190` + - `mkdir -p build/eng-artifacts/T7.190` + - `web/node_modules/.bin/eslint web/js/components/preact/WebRTCVideoCell.jsx web/js/components/preact/recordings/RecordingsTable.jsx web/js/components/preact/timeline/TimelinePage.jsx --format json --output-file build/eng-artifacts/T7.190/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.190/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/mouse-events-have-key-events'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.190`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.191: jsx-a11y/no-autofocus in AuthGate.jsx, LoginView.jsx, EventDestinationEditor.jsx, EventRouteEditor.jsx, CollectionManager.jsx, OrganizationManager.jsx +- **depends_on**: [T7, T10, T7.179, T7.181, T7.182] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/AuthGate.jsx`, `web/js/components/preact/LoginView.jsx`, `web/js/components/preact/events/EventDestinationEditor.jsx`, `web/js/components/preact/events/EventRouteEditor.jsx`, `web/js/components/preact/fleet/CollectionManager.jsx`, `web/js/components/preact/fleet/OrganizationManager.jsx`, `web/tests/remediation/t7_191.spec.jsx`, `quality/evidence/T7.191.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-20016996a3c7e096, ESLINT-BUCKET-92718cbd3fc79c29, ESLINT-BUCKET-71520b6429344586, ESLINT-BUCKET-82f90000b805b238, ESLINT-BUCKET-2acf09b0886c70c8, ESLINT-BUCKET-e6884093b3a77737 +- **acceptance_criteria**: + - Assert initial render does not steal focus; explicit dialog-open focus management must return focus to the invoker on close. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_191` + - `mkdir -p build/eng-artifacts/T7.191` + - `web/node_modules/.bin/eslint web/js/components/preact/AuthGate.jsx web/js/components/preact/LoginView.jsx web/js/components/preact/events/EventDestinationEditor.jsx web/js/components/preact/events/EventRouteEditor.jsx web/js/components/preact/fleet/CollectionManager.jsx web/js/components/preact/fleet/OrganizationManager.jsx --format json --output-file build/eng-artifacts/T7.191/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.191/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/no-autofocus'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.191`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.192: jsx-a11y/no-autofocus in InvestigationBookmarks.jsx, TotpSetupModal.jsx +- **depends_on**: [T7, T10, T7.182, T7.187] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/investigation/InvestigationBookmarks.jsx`, `web/js/components/preact/users/TotpSetupModal.jsx`, `web/tests/remediation/t7_192.spec.jsx`, `quality/evidence/T7.192.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-24d767472f9eac0b, ESLINT-BUCKET-6040ad491a730d6d +- **acceptance_criteria**: + - Assert initial render does not steal focus; explicit dialog-open focus management must return focus to the invoker on close. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_192` + - `mkdir -p build/eng-artifacts/T7.192` + - `web/node_modules/.bin/eslint web/js/components/preact/investigation/InvestigationBookmarks.jsx web/js/components/preact/users/TotpSetupModal.jsx --format json --output-file build/eng-artifacts/T7.192/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.192/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/no-autofocus'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.192`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.193: jsx-a11y/no-noninteractive-element-interactions in FisheyeEptzCanvas.jsx, StreamCard.jsx, ZoneEditor.jsx, LanguageSelector.jsx, InvestigationView.jsx, LiveBuildingPlan.jsx +- **depends_on**: [T7, T10, T7.189, T7.180, T7.162, T7.188, T7.183, T10.3] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/FisheyeEptzCanvas.jsx`, `web/js/components/preact/StreamCard.jsx`, `web/js/components/preact/ZoneEditor.jsx`, `web/js/components/preact/common/LanguageSelector.jsx`, `web/js/components/preact/investigation/InvestigationView.jsx`, `web/js/components/preact/live/LiveBuildingPlan.jsx`, `web/tests/remediation/t7_193.spec.jsx`, `quality/evidence/T7.193.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-d8c2127f8046b560, ESLINT-BUCKET-e4342be069acaf24, ESLINT-BUCKET-141e103765b7c505, ESLINT-BUCKET-370c727cb2690828, ESLINT-BUCKET-bf7012d93c48094c, ESLINT-BUCKET-11c6ba5b7bf02e40 +- **acceptance_criteria**: + - Remove unintended interactive behavior from structural elements or expose correct interactive semantics; preserve nested-control propagation and keyboard behavior. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_193` + - `mkdir -p build/eng-artifacts/T7.193` + - `web/node_modules/.bin/eslint web/js/components/preact/FisheyeEptzCanvas.jsx web/js/components/preact/StreamCard.jsx web/js/components/preact/ZoneEditor.jsx web/js/components/preact/common/LanguageSelector.jsx web/js/components/preact/investigation/InvestigationView.jsx web/js/components/preact/live/LiveBuildingPlan.jsx --format json --output-file build/eng-artifacts/T7.193/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.193/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/no-noninteractive-element-interactions'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.193`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: T7.162 0080a39f removes related occurrences: Language noninteractive-element 370c727cb2690828 2→0. Whole bucket/task remains open. +- **files edited/created**: + +### T7.194: jsx-a11y/no-noninteractive-element-interactions in LiveOperatorNavigator.jsx, RecordingsGrid.jsx, TimelineCursor.jsx, TimelineSegments.jsx, AccessPolicyModal.jsx, AddUserModal.jsx +- **depends_on**: [T7, T10, T7.183, T7.186, T10.3] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/live/LiveOperatorNavigator.jsx`, `web/js/components/preact/recordings/RecordingsGrid.jsx`, `web/js/components/preact/timeline/TimelineCursor.jsx`, `web/js/components/preact/timeline/TimelineSegments.jsx`, `web/js/components/preact/users/AccessPolicyModal.jsx`, `web/js/components/preact/users/AddUserModal.jsx`, `web/tests/remediation/t7_194.spec.jsx`, `quality/evidence/T7.194.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-87cfe9ce5d499faf, ESLINT-BUCKET-4d679d89b4da6865, ESLINT-BUCKET-d670cad0625d4f2c, ESLINT-BUCKET-03caeab009bdc4fd, ESLINT-BUCKET-939df4abd7adefdd, ESLINT-BUCKET-4cf2eab3dda13b90 +- **acceptance_criteria**: + - Remove unintended interactive behavior from structural elements or expose correct interactive semantics; preserve nested-control propagation and keyboard behavior. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_194` + - `mkdir -p build/eng-artifacts/T7.194` + - `web/node_modules/.bin/eslint web/js/components/preact/live/LiveOperatorNavigator.jsx web/js/components/preact/recordings/RecordingsGrid.jsx web/js/components/preact/timeline/TimelineCursor.jsx web/js/components/preact/timeline/TimelineSegments.jsx web/js/components/preact/users/AccessPolicyModal.jsx web/js/components/preact/users/AddUserModal.jsx --format json --output-file build/eng-artifacts/T7.194/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.194/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/no-noninteractive-element-interactions'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.194`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: Related exact user-modal boundary removals are recorded in T7.164/71dbd4c1 and the map; no whole-task closure inferred. +- **files edited/created**: + +### T7.195: jsx-a11y/no-noninteractive-element-interactions in ApiKeyModal.jsx, AuditHistoryModal.jsx, EditUserModal.jsx, RoleManagerModal.jsx +- **depends_on**: [T7, T10, T7.186, T7.187] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/users/ApiKeyModal.jsx`, `web/js/components/preact/users/AuditHistoryModal.jsx`, `web/js/components/preact/users/EditUserModal.jsx`, `web/js/components/preact/users/RoleManagerModal.jsx`, `web/tests/remediation/t7_195.spec.jsx`, `quality/evidence/T7.195.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-68103860cfe78d89, ESLINT-BUCKET-be996c224380b85e, ESLINT-BUCKET-65f403c439392041, ESLINT-BUCKET-752682010e0c301c +- **acceptance_criteria**: + - Remove unintended interactive behavior from structural elements or expose correct interactive semantics; preserve nested-control propagation and keyboard behavior. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_195` + - `mkdir -p build/eng-artifacts/T7.195` + - `web/node_modules/.bin/eslint web/js/components/preact/users/ApiKeyModal.jsx web/js/components/preact/users/AuditHistoryModal.jsx web/js/components/preact/users/EditUserModal.jsx web/js/components/preact/users/RoleManagerModal.jsx --format json --output-file build/eng-artifacts/T7.195/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.195/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/no-noninteractive-element-interactions'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.195`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: Related occurrence already resolved by T7.165 (fe93b78c): RoleManagerModal no-noninteractive-element-interactions1→0. Recheck after dependencies and other owned files before closing this task; evidence quality/evidence/T7.165.md. Related exact user-modal boundary removals are recorded in T7.164/71dbd4c1 and the map; no whole-task closure inferred. +- **files edited/created**: + +### T7.196: jsx-a11y/no-noninteractive-tabindex in FisheyeEptzCanvas.jsx, LanguageSelector.jsx +- **depends_on**: [T7, T10, T7.193] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/FisheyeEptzCanvas.jsx`, `web/js/components/preact/common/LanguageSelector.jsx`, `web/tests/remediation/t7_196.spec.jsx`, `quality/evidence/T7.196.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-f32c4fd5a51337d3, ESLINT-BUCKET-d5958c65428cdc31 +- **acceptance_criteria**: + - Assert the tab order includes only intended interactive controls; keyboard navigation of composite widgets must follow their documented role. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_196` + - `mkdir -p build/eng-artifacts/T7.196` + - `web/node_modules/.bin/eslint web/js/components/preact/FisheyeEptzCanvas.jsx web/js/components/preact/common/LanguageSelector.jsx --format json --output-file build/eng-artifacts/T7.196/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.196/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/no-noninteractive-tabindex'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.196`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: T7.162 0080a39f removes related occurrences: Language tabindex d5958c65428cdc31 1→0; other component remains open. Whole bucket/task remains open. +- **files edited/created**: + +### T7.197: jsx-a11y/no-static-element-interactions in GridPicker.jsx, HLSVideoCell.jsx, HealthView.jsx, LiveView.jsx, MSEVideoCell.jsx, StreamCard.jsx +- **depends_on**: [T7, T10, T7.161, T7.189, T7.179, T7.188, T7.193] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/GridPicker.jsx`, `web/js/components/preact/HLSVideoCell.jsx`, `web/js/components/preact/HealthView.jsx`, `web/js/components/preact/LiveView.jsx`, `web/js/components/preact/MSEVideoCell.jsx`, `web/js/components/preact/StreamCard.jsx`, `web/tests/remediation/t7_197.spec.jsx`, `quality/evidence/T7.197.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-45ebdb33421d89d5, ESLINT-BUCKET-d6af678aacfbb026, ESLINT-BUCKET-e48ab90f6d76f69a, ESLINT-BUCKET-6cf65d94c0e817c8, ESLINT-BUCKET-d0493f3174391c77, ESLINT-BUCKET-134e1087e8cda477 +- **acceptance_criteria**: + - Make reported actions native interactive controls or provide correct role, focus and keyboard activation; pointer and keyboard must execute the same action once. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_197` + - `mkdir -p build/eng-artifacts/T7.197` + - `web/node_modules/.bin/eslint web/js/components/preact/GridPicker.jsx web/js/components/preact/HLSVideoCell.jsx web/js/components/preact/HealthView.jsx web/js/components/preact/LiveView.jsx web/js/components/preact/MSEVideoCell.jsx web/js/components/preact/StreamCard.jsx --format json --output-file build/eng-artifacts/T7.197/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.197/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/no-static-element-interactions'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.197`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: T7.161 d8552365 removes related static occurrences Grid45ebdb33421d89d5 2→1,HLSd6af678aacfbb026 2→0,Healthe48ab90f6d76f69a 1→0,MSEd0493f3174391c77 1→0,StreamCard134e1087e8cda477 1→0. Remaining buckets stayopen. +- **files edited/created**: + +### T7.198: jsx-a11y/no-static-element-interactions in StreamConfigModal.jsx, StreamsView.jsx, ToastContainer.jsx, UI.jsx, WebRTCVideoCell.jsx, ZoneEditor.jsx +- **depends_on**: [T7, T10, T7.180, T7.189, T7.162, T7.188, T7.190, T7.193, T10.2] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/StreamConfigModal.jsx`, `web/js/components/preact/StreamsView.jsx`, `web/js/components/preact/ToastContainer.jsx`, `web/js/components/preact/UI.jsx`, `web/js/components/preact/WebRTCVideoCell.jsx`, `web/js/components/preact/ZoneEditor.jsx`, `web/tests/remediation/t7_198.spec.jsx`, `quality/evidence/T7.198.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-8a059d804561f236, ESLINT-BUCKET-21e6c2fa9c26c964, ESLINT-BUCKET-b18881a630321191, ESLINT-BUCKET-d9d81163d1e1193d, ESLINT-BUCKET-3322f39793a9258c, ESLINT-BUCKET-6bdf7c909180cb5e +- **acceptance_criteria**: + - Make reported actions native interactive controls or provide correct role, focus and keyboard activation; pointer and keyboard must execute the same action once. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_198` + - `mkdir -p build/eng-artifacts/T7.198` + - `web/node_modules/.bin/eslint web/js/components/preact/StreamConfigModal.jsx web/js/components/preact/StreamsView.jsx web/js/components/preact/ToastContainer.jsx web/js/components/preact/UI.jsx web/js/components/preact/WebRTCVideoCell.jsx web/js/components/preact/ZoneEditor.jsx --format json --output-file build/eng-artifacts/T7.198/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.198/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/no-static-element-interactions'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.198`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: T7.161 d8552365 removes Streams static21e6c2fa9c26c964 2→0; other buckets remainopen. T7.162 0080a39f removes related occurrences: Toast static b18881a630321191 1→0, UI d9d81163d1e1193d 3→1, WebRTC 3322f39793a9258c 1→0, Zone 6bdf7c909180cb5e 1→0. Whole bucket/task remains open. +- **files edited/created**: + +### T7.199: jsx-a11y/no-static-element-interactions in ModalDialog.jsx, BulkOrganizeModal.jsx, CollectionManager.jsx, OrganizationManager.jsx, LiveBuildingPlan.jsx, LiveOperatorNavigator.jsx +- **depends_on**: [T7, T10, T7.180, T7.181, T7.191, T7.193, T7.194, T10.3] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/common/ModalDialog.jsx`, `web/js/components/preact/fleet/BulkOrganizeModal.jsx`, `web/js/components/preact/fleet/CollectionManager.jsx`, `web/js/components/preact/fleet/OrganizationManager.jsx`, `web/js/components/preact/live/LiveBuildingPlan.jsx`, `web/js/components/preact/live/LiveOperatorNavigator.jsx`, `web/tests/remediation/t7_199.spec.jsx`, `quality/evidence/T7.199.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-243fa74ece4acc83, ESLINT-BUCKET-aa87081e964848f0, ESLINT-BUCKET-b9126919c0a748d2, ESLINT-BUCKET-ac70bbab0047fa80, ESLINT-BUCKET-23073b0c71d7d2e3, ESLINT-BUCKET-be705595f12d1850 +- **acceptance_criteria**: + - Make reported actions native interactive controls or provide correct role, focus and keyboard activation; pointer and keyboard must execute the same action once. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_199` + - `mkdir -p build/eng-artifacts/T7.199` + - `web/node_modules/.bin/eslint web/js/components/preact/common/ModalDialog.jsx web/js/components/preact/fleet/BulkOrganizeModal.jsx web/js/components/preact/fleet/CollectionManager.jsx web/js/components/preact/fleet/OrganizationManager.jsx web/js/components/preact/live/LiveBuildingPlan.jsx web/js/components/preact/live/LiveOperatorNavigator.jsx --format json --output-file build/eng-artifacts/T7.199/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.199/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/no-static-element-interactions'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.199`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: T7.163 (29fa0d50) removes related no-static occurrences: Live be705595f12d1850 1→0; Recordings55a74c5d3180cf15 4→1; Go2rtcfd743aa60d9abb1f 2→0; Clearfbc7ca1e0083c1d1 1→0; Restart259ca1824a1d1447 1→0; Timelinec06de8e6c5473e61 2→1. Remaining hover/mouse guards and other buckets keep this task open. T7.162 0080a39f removes related occurrences: ModalDialog static 243fa74ece4acc83 1→0. Whole bucket/task remains open. +- **files edited/created**: + +### T7.200: jsx-a11y/no-static-element-interactions in RecordingsGrid.jsx, Go2rtcTab.jsx, ClearLogsModal.jsx, RestartModal.jsx, TimelinePlayer.jsx, AccessPolicyModal.jsx +- **depends_on**: [T7, T10, T7.194, T7.184, T7.163, T7.188, T10.2, T10.3] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/recordings/RecordingsGrid.jsx`, `web/js/components/preact/settings/Go2rtcTab.jsx`, `web/js/components/preact/system/ClearLogsModal.jsx`, `web/js/components/preact/system/RestartModal.jsx`, `web/js/components/preact/timeline/TimelinePlayer.jsx`, `web/js/components/preact/users/AccessPolicyModal.jsx`, `web/tests/remediation/t7_200.spec.jsx`, `quality/evidence/T7.200.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-55a74c5d3180cf15, ESLINT-BUCKET-fd743aa60d9abb1f, ESLINT-BUCKET-fbc7ca1e0083c1d1, ESLINT-BUCKET-259ca1824a1d1447, ESLINT-BUCKET-c06de8e6c5473e61, ESLINT-BUCKET-6bab06cb3c8102a9 +- **acceptance_criteria**: + - Make reported actions native interactive controls or provide correct role, focus and keyboard activation; pointer and keyboard must execute the same action once. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_200` + - `mkdir -p build/eng-artifacts/T7.200` + - `web/node_modules/.bin/eslint web/js/components/preact/recordings/RecordingsGrid.jsx web/js/components/preact/settings/Go2rtcTab.jsx web/js/components/preact/system/ClearLogsModal.jsx web/js/components/preact/system/RestartModal.jsx web/js/components/preact/timeline/TimelinePlayer.jsx web/js/components/preact/users/AccessPolicyModal.jsx --format json --output-file build/eng-artifacts/T7.200/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.200/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/no-static-element-interactions'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.200`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: Related exact user-modal boundary removals are recorded in T7.164/71dbd4c1 and the map; no whole-task closure inferred. T7.163 (29fa0d50) removes related no-static occurrences: Live be705595f12d1850 1→0; Recordings55a74c5d3180cf15 4→1; Go2rtcfd743aa60d9abb1f 2→0; Clearfbc7ca1e0083c1d1 1→0; Restart259ca1824a1d1447 1→0; Timelinec06de8e6c5473e61 2→1. Remaining hover/mouse guards and other buckets keep this task open. +- **files edited/created**: + +### T7.201: jsx-a11y/no-static-element-interactions in AddUserModal.jsx, ApiKeyModal.jsx, AuditHistoryModal.jsx, DeleteUserModal.jsx, EditUserModal.jsx, RoleManagerModal.jsx +- **depends_on**: [T7, T10, T7.194, T7.195, T7.164] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/users/AddUserModal.jsx`, `web/js/components/preact/users/ApiKeyModal.jsx`, `web/js/components/preact/users/AuditHistoryModal.jsx`, `web/js/components/preact/users/DeleteUserModal.jsx`, `web/js/components/preact/users/EditUserModal.jsx`, `web/js/components/preact/users/RoleManagerModal.jsx`, `web/tests/remediation/t7_201.spec.jsx`, `quality/evidence/T7.201.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-3ace786e8c2cd1a8, ESLINT-BUCKET-08d8785eb7476330, ESLINT-BUCKET-51093f104f4e1cbb, ESLINT-BUCKET-7c82f257c4fb1103, ESLINT-BUCKET-c4dd2be3483455e5, ESLINT-BUCKET-5c8c07852ed0b107 +- **acceptance_criteria**: + - Make reported actions native interactive controls or provide correct role, focus and keyboard activation; pointer and keyboard must execute the same action once. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_201` + - `mkdir -p build/eng-artifacts/T7.201` + - `web/node_modules/.bin/eslint web/js/components/preact/users/AddUserModal.jsx web/js/components/preact/users/ApiKeyModal.jsx web/js/components/preact/users/AuditHistoryModal.jsx web/js/components/preact/users/DeleteUserModal.jsx web/js/components/preact/users/EditUserModal.jsx web/js/components/preact/users/RoleManagerModal.jsx --format json --output-file build/eng-artifacts/T7.201/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.201/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/no-static-element-interactions'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.201`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: Related occurrence already resolved by T7.165 (fe93b78c): RoleManagerModal no-static-element-interactions1→0. Recheck after dependencies and other owned files before closing this task; evidence quality/evidence/T7.165.md. Related exact user-modal boundary removals are recorded in T7.164/71dbd4c1 and the map; no whole-task closure inferred. +- **files edited/created**: + +### T7.202: jsx-a11y/no-static-element-interactions in TotpSetupModal.jsx +- **depends_on**: [T7, T10, T7.192] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/users/TotpSetupModal.jsx`, `web/tests/remediation/t7_202.spec.jsx`, `quality/evidence/T7.202.md`. +- **priority**: low +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: ESLINT-BUCKET-e046fbe269eed0dc +- **acceptance_criteria**: + - Make reported actions native interactive controls or provide correct role, focus and keyboard activation; pointer and keyboard must execute the same action once. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_202` + - `mkdir -p build/eng-artifacts/T7.202` + - `web/node_modules/.bin/eslint web/js/components/preact/users/TotpSetupModal.jsx --format json --output-file build/eng-artifacts/T7.202/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.202/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'jsx-a11y/no-static-element-interactions'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.202`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: Related occurrence already resolved by T7.165 (fe93b78c): TotpSetupModal no-static-element-interactions2→0. Recheck after dependencies and other owned files before closing this task; evidence quality/evidence/T7.165.md. +- **files edited/created**: + +### T7.203: nullPointerArithmeticRedundantCheck in sod_img_writer.h +- **depends_on**: [T7, T13] +- **agent_type**: local +- **ownership**: `quality/reviews/t7_203-upstream.json`, `quality/evidence/T7.203.md`. +- **priority**: low +- **description**: Review pinned upstream source and reproduce the listed diagnostics in the actual linked feature profile. Record precise upstream fix/version or evidence-backed false-positive analysis. Do not edit system headers or vendored/submodule source as an untracked patch. If an update/local patch is needed, append an exact bounded integration child, dependency and regression command to T33 before closing this task. Missing upstream or feature evidence blocks completion. +- **findings**: CPPCHECK-38d664fd8e77f607, CPPCHECK-3d1693ad77a15fa2, CPPCHECK-3508a7db3ef7fc03, CPPCHECK-ca79e52beb5f22bb, CPPCHECK-5941fdb9ccb989f3 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 -m json.tool quality/reviews/t7_203-upstream.json` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.203`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.204: nullPointerRedundantCheck in sod_img_writer.h +- **depends_on**: [T7, T13] +- **agent_type**: local +- **ownership**: `quality/reviews/t7_204-upstream.json`, `quality/evidence/T7.204.md`. +- **priority**: low +- **description**: Review pinned upstream source and reproduce the listed diagnostics in the actual linked feature profile. Record precise upstream fix/version or evidence-backed false-positive analysis. Do not edit system headers or vendored/submodule source as an untracked patch. If an update/local patch is needed, append an exact bounded integration child, dependency and regression command to T33 before closing this task. Missing upstream or feature evidence blocks completion. +- **findings**: CPPCHECK-10a0fa2f24fefc90, CPPCHECK-ffdd97ac6ebb6b4b, CPPCHECK-7dc87bf617f52146, CPPCHECK-347fec696ca3fcab, CPPCHECK-1980a8233237856a +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 -m json.tool quality/reviews/t7_204-upstream.json` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.204`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.205: wrongPrintfScanfArgNum in linux_thermal.c +- **depends_on**: [T7, T13, T7.95] +- **agent_type**: worker +- **ownership**: `src/telemetry/collectors/linux_thermal.c`, `tests/unit/test_t7_205.c`, `tests/unit/cmake/eng/t7_205.cmake`, `quality/evidence/T7.205.md`. +- **priority**: low +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-e9cd8f29e136cbb8 +- **acceptance_criteria**: + - Exercise every reported format/library call with boundary values; argument types/counts, buffer extents and returned errors must match the library contract with no UB. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.205 --regex '^test_t7_205$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.205`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.206: zerodivcond in sod_img_reader.h +- **depends_on**: [T7, T13] +- **agent_type**: local +- **ownership**: `quality/reviews/t7_206-upstream.json`, `quality/evidence/T7.206.md`. +- **priority**: low +- **description**: Review pinned upstream source and reproduce the listed diagnostics in the actual linked feature profile. Record precise upstream fix/version or evidence-backed false-positive analysis. Do not edit system headers or vendored/submodule source as an untracked patch. If an update/local patch is needed, append an exact bounded integration child, dependency and regression command to T33 before closing this task. Missing upstream or feature evidence blocks completion. +- **findings**: CPPCHECK-ac6f81ae03a685f3 +- **acceptance_criteria**: + - Exercise signed/unsigned boundaries, negative/zero values, maximum accepted values and multiplication overflow before conversion; assert valid results are unchanged and invalid values cannot wrap, divide by zero or allocate oversized memory. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 -m json.tool quality/reviews/t7_206-upstream.json` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.206`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.207: arrayIndexThenCheck in sod_img_writer.h +- **depends_on**: [T7, T13] +- **agent_type**: local +- **ownership**: `quality/reviews/t7_207-upstream.json`, `quality/evidence/T7.207.md`. +- **priority**: info +- **description**: Review pinned upstream source and reproduce the listed diagnostics in the actual linked feature profile. Record precise upstream fix/version or evidence-backed false-positive analysis. Do not edit system headers or vendored/submodule source as an untracked patch. If an update/local patch is needed, append an exact bounded integration child, dependency and regression command to T33 before closing this task. Missing upstream or feature evidence blocks completion. +- **findings**: CPPCHECK-BUCKET-8bb35cef096bd68d +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 -m json.tool quality/reviews/t7_207-upstream.json` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.207`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.208: constParameter in linux_network.c +- **depends_on**: [T7, T13, T7.95] +- **agent_type**: worker +- **ownership**: `src/telemetry/collectors/linux_network.c`, `tests/unit/test_t7_208.c`, `tests/unit/cmake/eng/t7_208.cmake`, `quality/evidence/T7.208.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-7e66ded89b37cb4f +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.208 --regex '^test_t7_208$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.208`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.209: constParameter in api_handlers_investigations.c +- **depends_on**: [T7, T13, T7.148] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_investigations.c`, `tests/unit/test_t7_209.c`, `tests/unit/cmake/eng/t7_209.cmake`, `quality/evidence/T7.209.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-c6d1df4e6770f40e +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.209 --regex '^test_t7_209$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.209`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.210: constParameterCallback in sod_img_reader.h, sod_img_writer.h +- **depends_on**: [T7, T13] +- **agent_type**: local +- **ownership**: `quality/reviews/t7_210-upstream.json`, `quality/evidence/T7.210.md`. +- **priority**: info +- **description**: Review pinned upstream source and reproduce the listed diagnostics in the actual linked feature profile. Record precise upstream fix/version or evidence-backed false-positive analysis. Do not edit system headers or vendored/submodule source as an untracked patch. If an update/local patch is needed, append an exact bounded integration child, dependency and regression command to T33 before closing this task. Missing upstream or feature evidence blocks completion. +- **findings**: CPPCHECK-BUCKET-fb37582a72f59582, CPPCHECK-BUCKET-c7b3ef530f996e26 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 -m json.tool quality/reviews/t7_210-upstream.json` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.210`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.211: constParameterCallback in storage_migration.c +- **depends_on**: [T7, T13, T7.138] +- **agent_type**: worker +- **ownership**: `src/storage/storage_migration.c`, `tests/unit/test_t7_211.c`, `tests/unit/cmake/eng/t7_211.cmake`, `quality/evidence/T7.211.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-90ec02278e01bcac +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.211 --regex '^test_t7_211$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.211`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.212: constParameterCallback in yaml_redact.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/utils/yaml_redact.c`, `tests/unit/test_t7_212.c`, `tests/unit/cmake/eng/t7_212.cmake`, `quality/evidence/T7.212.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-6291a48798ed350c +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.212 --regex '^test_t7_212$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.212`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.213: constParameterCallback in api_detection.c +- **depends_on**: [T7, T13, T7.112] +- **agent_type**: worker +- **ownership**: `src/video/api_detection.c`, `tests/unit/test_t7_213.c`, `tests/unit/cmake/eng/t7_213.cmake`, `quality/evidence/T7.213.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-681aedac30d87a1e +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.213 --regex '^test_t7_213$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.213`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.214: constParameterCallback in go2rtc_api.c, go2rtc_integration.c, go2rtc_snapshot.c +- **depends_on**: [T7, T13, T7.98, T7.154] +- **agent_type**: worker +- **ownership**: `src/video/go2rtc/go2rtc_api.c`, `src/video/go2rtc/go2rtc_integration.c`, `src/video/go2rtc/go2rtc_snapshot.c`, `tests/unit/test_t7_214.c`, `tests/unit/cmake/eng/t7_214.cmake`, `quality/evidence/T7.214.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-ee781b371da39bf5, CPPCHECK-BUCKET-dc056676c7084f53, CPPCHECK-BUCKET-0130dae8fb623afa +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.214 --regex '^test_t7_214$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.214`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.215: constParameterCallback in onvif_detection.c +- **depends_on**: [T7, T13, T7.146] +- **agent_type**: worker +- **ownership**: `src/video/onvif_detection.c`, `tests/unit/test_t7_215.c`, `tests/unit/cmake/eng/t7_215.cmake`, `quality/evidence/T7.215.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-365ebbda46eacf74 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.215 --regex '^test_t7_215$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.215`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.216: constParameterCallback in onvif_device_management.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/video/onvif_device_management.c`, `tests/unit/test_t7_216.c`, `tests/unit/cmake/eng/t7_216.cmake`, `quality/evidence/T7.216.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-8737ababcc06f484 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.216 --regex '^test_t7_216$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.216`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.217: constParameterCallback in onvif_imaging.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/video/onvif_imaging.c`, `tests/unit/test_t7_217.c`, `tests/unit/cmake/eng/t7_217.cmake`, `quality/evidence/T7.217.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-baf28ba17bc25dd0 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.217 --regex '^test_t7_217$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.217`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.218: constParameterCallback in onvif_ptz.c +- **depends_on**: [T7, T13, T7.156] +- **agent_type**: worker +- **ownership**: `src/video/onvif_ptz.c`, `tests/unit/test_t7_218.c`, `tests/unit/cmake/eng/t7_218.cmake`, `quality/evidence/T7.218.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-462bf13ae3867115 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.218 --regex '^test_t7_218$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.218`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.219: constParameterCallback in api_handlers_go2rtc_proxy.c +- **depends_on**: [T7, T13, T7.147] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_go2rtc_proxy.c`, `tests/unit/test_t7_219.c`, `tests/unit/cmake/eng/t7_219.cmake`, `quality/evidence/T7.219.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-b6f82c4c9b287eba +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.219 --regex '^test_t7_219$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.219`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.220: constParameterCallback in api_handlers_health.c +- **depends_on**: [T7, T13, T7.158] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_health.c`, `tests/unit/test_t7_220.c`, `tests/unit/cmake/eng/t7_220.cmake`, `quality/evidence/T7.220.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-c7a8e9a51802c60d +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.220 --regex '^test_t7_220$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.220`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.221: constParameterCallback in go2rtc_proxy_thread.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/web/go2rtc_proxy_thread.c`, `tests/unit/test_t7_221.c`, `tests/unit/cmake/eng/t7_221.cmake`, `quality/evidence/T7.221.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-d3985740b2ac6585 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.221 --regex '^test_t7_221$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.221`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.222: constParameterPointer in sod_img_reader.h, sod_img_writer.h +- **depends_on**: [T7, T13] +- **agent_type**: local +- **ownership**: `quality/reviews/t7_222-upstream.json`, `quality/evidence/T7.222.md`. +- **priority**: info +- **description**: Review pinned upstream source and reproduce the listed diagnostics in the actual linked feature profile. Record precise upstream fix/version or evidence-backed false-positive analysis. Do not edit system headers or vendored/submodule source as an untracked patch. If an update/local patch is needed, append an exact bounded integration child, dependency and regression command to T33 before closing this task. Missing upstream or feature evidence blocks completion. +- **findings**: CPPCHECK-BUCKET-82a085f0cc04468f, CPPCHECK-BUCKET-27ab7ab1e74c2e72 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 -m json.tool quality/reviews/t7_222-upstream.json` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.222`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.223: constParameterPointer in authorization.c +- **depends_on**: [T7, T13, T7.114] +- **agent_type**: worker +- **ownership**: `src/core/authorization.c`, `tests/unit/test_t7_223.c`, `tests/unit/cmake/eng/t7_223.cmake`, `quality/evidence/T7.223.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-d0de2e4ac47c3191 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.223 --regex '^test_t7_223$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.223`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.224: constParameterPointer in mqtt_destination_client.c +- **depends_on**: [T7, T13, T7.134] +- **agent_type**: worker +- **ownership**: `src/core/mqtt_destination_client.c`, `tests/unit/test_t7_224.c`, `tests/unit/cmake/eng/t7_224.cmake`, `quality/evidence/T7.224.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-fbb9e9402242eaba +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.224 --regex '^test_t7_224$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.224`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.225: constParameterPointer in db_camera_collections.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/database/db_camera_collections.c`, `tests/unit/test_t7_225.c`, `tests/unit/cmake/eng/t7_225.cmake`, `quality/evidence/T7.225.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-fd8d6a4b8cc77400 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.225 --regex '^test_t7_225$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.225`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.226: constParameterPointer in ezxml.c +- **depends_on**: [T7, T13] +- **agent_type**: local +- **ownership**: `quality/reviews/t7_226-upstream.json`, `quality/evidence/T7.226.md`. +- **priority**: info +- **description**: Review pinned upstream source and reproduce the listed diagnostics in the actual linked feature profile. Record precise upstream fix/version or evidence-backed false-positive analysis. Do not edit system headers or vendored/submodule source as an untracked patch. If an update/local patch is needed, append an exact bounded integration child, dependency and regression command to T33 before closing this task. Missing upstream or feature evidence blocks completion. +- **findings**: CPPCHECK-BUCKET-43fcd9340a9e63ed +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 -m json.tool quality/reviews/t7_226-upstream.json` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.226`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.227: constParameterPointer in sod.c +- **depends_on**: [T7, T13] +- **agent_type**: local +- **ownership**: `quality/reviews/t7_227-upstream.json`, `quality/evidence/T7.227.md`. +- **priority**: info +- **description**: Review pinned upstream source and reproduce the listed diagnostics in the actual linked feature profile. Record precise upstream fix/version or evidence-backed false-positive analysis. Do not edit system headers or vendored/submodule source as an untracked patch. If an update/local patch is needed, append an exact bounded integration child, dependency and regression command to T33 before closing this task. Missing upstream or feature evidence blocks completion. +- **findings**: CPPCHECK-BUCKET-e7f8f80f0cd95f0d +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 -m json.tool quality/reviews/t7_227-upstream.json` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.227`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.228: constParameterPointer in linux_cgroup.c +- **depends_on**: [T7, T13, T7.83] +- **agent_type**: worker +- **ownership**: `src/telemetry/collectors/linux_cgroup.c`, `tests/unit/test_t7_228.c`, `tests/unit/cmake/eng/t7_228.cmake`, `quality/evidence/T7.228.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-70e0496568faa30f +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.228 --regex '^test_t7_228$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.228`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.229: constParameterPointer in nvme_provider.c +- **depends_on**: [T7, T13, T7.76] +- **agent_type**: worker +- **ownership**: `src/telemetry/providers/nvme_provider.c`, `tests/unit/test_t7_229.c`, `tests/unit/cmake/eng/t7_229.cmake`, `quality/evidence/T7.229.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-5bdf073f41a6c65d +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.229 --regex '^test_t7_229$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.229`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.230: constParameterPointer in system_health_evaluator.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/telemetry/system_health_evaluator.c`, `tests/unit/test_t7_230.c`, `tests/unit/cmake/eng/t7_230.cmake`, `quality/evidence/T7.230.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-527aa4750d3ac84a +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.230 --regex '^test_t7_230$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.230`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.231: constParameterPointer in go2rtc_base_path.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/utils/go2rtc_base_path.c`, `tests/unit/test_t7_231.c`, `tests/unit/cmake/eng/t7_231.cmake`, `quality/evidence/T7.231.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-d9684d3aa203568c +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.231 --regex '^test_t7_231$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.231`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.232: constParameterPointer in strings.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/utils/strings.c`, `tests/unit/test_t7_232.c`, `tests/unit/cmake/eng/t7_232.cmake`, `quality/evidence/T7.232.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-e228634622467328 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.232 --regex '^test_t7_232$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.232`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.233: constParameterPointer in faac_encoder.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/video/faac_encoder.c`, `tests/unit/test_t7_233.c`, `tests/unit/cmake/eng/t7_233.cmake`, `quality/evidence/T7.233.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-bdb708e597b5cade +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.233 --regex '^test_t7_233$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.233`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.234: constParameterPointer in hls_unified_thread.c +- **depends_on**: [T7, T13, T7.69] +- **agent_type**: worker +- **ownership**: `src/video/hls/hls_unified_thread.c`, `tests/unit/test_t7_234.c`, `tests/unit/cmake/eng/t7_234.cmake`, `quality/evidence/T7.234.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-cbab871259b9963d +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.234 --regex '^test_t7_234$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.234`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.235: constParameterPointer in unified_detection_thread.c +- **depends_on**: [T7, T13, T7.122] +- **agent_type**: worker +- **ownership**: `src/video/unified_detection_thread.c`, `tests/unit/test_t7_235.c`, `tests/unit/cmake/eng/t7_235.cmake`, `quality/evidence/T7.235.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-8c644cc847574bfe +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.235 --regex '^test_t7_235$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.235`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.236: constParameterPointer in go2rtc_websocket.c +- **depends_on**: [T7, T13, T7.150] +- **agent_type**: worker +- **ownership**: `src/web/go2rtc_websocket.c`, `tests/unit/test_t7_236.c`, `tests/unit/cmake/eng/t7_236.cmake`, `quality/evidence/T7.236.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-4c0052bc9de6e496 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.236 --regex '^test_t7_236$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.236`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.237: constParameterPointer in recording_archive.c +- **depends_on**: [T7, T13, T7.141] +- **agent_type**: worker +- **ownership**: `src/web/recording_archive.c`, `tests/unit/test_t7_237.c`, `tests/unit/cmake/eng/t7_237.cmake`, `quality/evidence/T7.237.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-45cfee19afe8668f +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.237 --regex '^test_t7_237$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.237`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.238: constVariable in sod_img_reader.h, sod_img_writer.h +- **depends_on**: [T7, T13] +- **agent_type**: local +- **ownership**: `quality/reviews/t7_238-upstream.json`, `quality/evidence/T7.238.md`. +- **priority**: info +- **description**: Review pinned upstream source and reproduce the listed diagnostics in the actual linked feature profile. Record precise upstream fix/version or evidence-backed false-positive analysis. Do not edit system headers or vendored/submodule source as an untracked patch. If an update/local patch is needed, append an exact bounded integration child, dependency and regression command to T33 before closing this task. Missing upstream or feature evidence blocks completion. +- **findings**: CPPCHECK-BUCKET-933d7ae37be3c395, CPPCHECK-BUCKET-b1c0c559c6e17a23 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 -m json.tool quality/reviews/t7_238-upstream.json` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.238`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.239: constVariable in mqtt_delivery_worker.c +- **depends_on**: [T7, T13, T7.91] +- **agent_type**: worker +- **ownership**: `src/core/mqtt_delivery_worker.c`, `tests/unit/test_t7_239.c`, `tests/unit/cmake/eng/t7_239.cmake`, `quality/evidence/T7.239.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-8596888f0dd1cc7e +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.239 --regex '^test_t7_239$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.239`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.240: constVariable in sod.c +- **depends_on**: [T7, T13] +- **agent_type**: local +- **ownership**: `quality/reviews/t7_240-upstream.json`, `quality/evidence/T7.240.md`. +- **priority**: info +- **description**: Review pinned upstream source and reproduce the listed diagnostics in the actual linked feature profile. Record precise upstream fix/version or evidence-backed false-positive analysis. Do not edit system headers or vendored/submodule source as an untracked patch. If an update/local patch is needed, append an exact bounded integration child, dependency and regression command to T33 before closing this task. Missing upstream or feature evidence blocks completion. +- **findings**: CPPCHECK-BUCKET-298feb4a490d6252 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 -m json.tool quality/reviews/t7_240-upstream.json` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.240`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.241: constVariable in sod_ingenic_mxu.c +- **depends_on**: [T7, T13] +- **agent_type**: local +- **ownership**: `quality/reviews/t7_241-upstream.json`, `quality/evidence/T7.241.md`. +- **priority**: info +- **description**: Review pinned upstream source and reproduce the listed diagnostics in the actual linked feature profile. Record precise upstream fix/version or evidence-backed false-positive analysis. Do not edit system headers or vendored/submodule source as an untracked patch. If an update/local patch is needed, append an exact bounded integration child, dependency and regression command to T33 before closing this task. Missing upstream or feature evidence blocks completion. +- **findings**: CPPCHECK-BUCKET-a8ce2c3168c021de +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 -m json.tool quality/reviews/t7_241-upstream.json` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.241`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.242: constVariablePointer in sod_img_reader.h, sod_img_writer.h +- **depends_on**: [T7, T13] +- **agent_type**: local +- **ownership**: `quality/reviews/t7_242-upstream.json`, `quality/evidence/T7.242.md`. +- **priority**: info +- **description**: Review pinned upstream source and reproduce the listed diagnostics in the actual linked feature profile. Record precise upstream fix/version or evidence-backed false-positive analysis. Do not edit system headers or vendored/submodule source as an untracked patch. If an update/local patch is needed, append an exact bounded integration child, dependency and regression command to T33 before closing this task. Missing upstream or feature evidence blocks completion. +- **findings**: CPPCHECK-BUCKET-9575437f939001d5, CPPCHECK-BUCKET-df230b0a5672f192 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 -m json.tool quality/reviews/t7_242-upstream.json` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.242`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.243: constVariablePointer in path_utils.c +- **depends_on**: [T7, T13, T7.128, T7.2.8] +- **agent_type**: worker +- **ownership**: `src/core/path_utils.c`, `tests/unit/test_t7_243.c`, `tests/unit/cmake/eng/t7_243.cmake`, `quality/evidence/T7.243.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-8e110c5360beb299 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.243 --regex '^test_t7_243$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.243`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.244: constVariablePointer in db_auth.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/database/db_auth.c`, `tests/unit/test_t7_244.c`, `tests/unit/cmake/eng/t7_244.cmake`, `quality/evidence/T7.244.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-6c5cf9bd3f338f84 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.244 --regex '^test_t7_244$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.244`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.245: constVariablePointer in db_authorization.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/database/db_authorization.c`, `tests/unit/test_t7_245.c`, `tests/unit/cmake/eng/t7_245.cmake`, `quality/evidence/T7.245.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-08f60b7ec76ce9d3 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.245 --regex '^test_t7_245$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.245`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.246: constVariablePointer in db_camera_tags.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/database/db_camera_tags.c`, `tests/unit/test_t7_246.c`, `tests/unit/cmake/eng/t7_246.cmake`, `quality/evidence/T7.246.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-7e7f6cc12d937123 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.246 --regex '^test_t7_246$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.246`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.247: constVariablePointer in db_core.c +- **depends_on**: [T7, T13, T7.25, T7.25.3] +- **agent_type**: worker +- **ownership**: `src/database/db_core.c`, `tests/unit/test_t7_247.c`, `tests/unit/cmake/eng/t7_247.cmake`, `quality/evidence/T7.247.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-f21c65912e355ace +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.247 --regex '^test_t7_247$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.247`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.248: constVariablePointer in db_fleet_query.c +- **depends_on**: [T7, T13, T7.113] +- **agent_type**: worker +- **ownership**: `src/database/db_fleet_query.c`, `tests/unit/test_t7_248.c`, `tests/unit/cmake/eng/t7_248.cmake`, `quality/evidence/T7.248.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-9f651ab0c8ea6422 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.248 --regex '^test_t7_248$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.248`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.249: constVariablePointer in db_storage_lifecycle.c +- **depends_on**: [T7, T13, T7.129] +- **agent_type**: worker +- **ownership**: `src/database/db_storage_lifecycle.c`, `tests/unit/test_t7_249.c`, `tests/unit/cmake/eng/t7_249.cmake`, `quality/evidence/T7.249.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-353deb8bba479029 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.249 --regex '^test_t7_249$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.249`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.250: constVariablePointer in ezxml.c +- **depends_on**: [T7, T13] +- **agent_type**: local +- **ownership**: `quality/reviews/t7_250-upstream.json`, `quality/evidence/T7.250.md`. +- **priority**: info +- **description**: Review pinned upstream source and reproduce the listed diagnostics in the actual linked feature profile. Record precise upstream fix/version or evidence-backed false-positive analysis. Do not edit system headers or vendored/submodule source as an untracked patch. If an update/local patch is needed, append an exact bounded integration child, dependency and regression command to T33 before closing this task. Missing upstream or feature evidence blocks completion. +- **findings**: CPPCHECK-BUCKET-c6ee03168003661f +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 -m json.tool quality/reviews/t7_250-upstream.json` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.250`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.251: constVariablePointer in sod.c +- **depends_on**: [T7, T13] +- **agent_type**: local +- **ownership**: `quality/reviews/t7_251-upstream.json`, `quality/evidence/T7.251.md`. +- **priority**: info +- **description**: Review pinned upstream source and reproduce the listed diagnostics in the actual linked feature profile. Record precise upstream fix/version or evidence-backed false-positive analysis. Do not edit system headers or vendored/submodule source as an untracked patch. If an update/local patch is needed, append an exact bounded integration child, dependency and regression command to T33 before closing this task. Missing upstream or feature evidence blocks completion. +- **findings**: CPPCHECK-BUCKET-5d8aaf042fefce78 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 -m json.tool quality/reviews/t7_251-upstream.json` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.251`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.252: constVariablePointer in storage_deletion.c +- **depends_on**: [T7, T13, T7.137, T28.1, T29.3] +- **agent_type**: worker +- **ownership**: `src/storage/storage_deletion.c`, `tests/unit/test_t7_252.c`, `tests/unit/cmake/eng/t7_252.cmake`, `quality/evidence/T7.252.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-b4e128a1af325d4f +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.252 --regex '^test_t7_252$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.252`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.253: constVariablePointer in storage_source.c +- **depends_on**: [T7, T13, T7.140] +- **agent_type**: worker +- **ownership**: `src/storage/storage_source.c`, `tests/unit/test_t7_253.c`, `tests/unit/cmake/eng/t7_253.cmake`, `quality/evidence/T7.253.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-77479b4ba7ef9849 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.253 --regex '^test_t7_253$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.253`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.254: constVariablePointer in linux_network.c, linux_process.c, linux_thermal.c +- **depends_on**: [T7, T13, T7.208, T7.95, T7.205] +- **agent_type**: worker +- **ownership**: `src/telemetry/collectors/linux_network.c`, `src/telemetry/collectors/linux_process.c`, `src/telemetry/collectors/linux_thermal.c`, `tests/unit/test_t7_254.c`, `tests/unit/cmake/eng/t7_254.cmake`, `quality/evidence/T7.254.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-c9404ed2a73b0977, CPPCHECK-BUCKET-e924700b1413accd, CPPCHECK-BUCKET-2ca7413944026780 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.254 --regex '^test_t7_254$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.254`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.255: constVariablePointer in linux_device_map.c, linux_hardware.c, nvme_provider.c +- **depends_on**: [T7, T13, T7.143, T7.229] +- **agent_type**: worker +- **ownership**: `src/telemetry/providers/linux_device_map.c`, `src/telemetry/providers/linux_hardware.c`, `src/telemetry/providers/nvme_provider.c`, `tests/unit/test_t7_255.c`, `tests/unit/cmake/eng/t7_255.cmake`, `quality/evidence/T7.255.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-a1edb744b382f8f5, CPPCHECK-BUCKET-4001e3b91223c7ad, CPPCHECK-BUCKET-ae15015525d96c08 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.255 --regex '^test_t7_255$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.255`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.256: constVariablePointer in system_health.c +- **depends_on**: [T7, T13, T7.59] +- **agent_type**: worker +- **ownership**: `src/telemetry/system_health.c`, `tests/unit/test_t7_256.c`, `tests/unit/cmake/eng/t7_256.cmake`, `quality/evidence/T7.256.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-f3bb2108694b562f +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.256 --regex '^test_t7_256$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.256`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.257: constVariablePointer in system_health_evaluator.c +- **depends_on**: [T7, T13, T7.230] +- **agent_type**: worker +- **ownership**: `src/telemetry/system_health_evaluator.c`, `tests/unit/test_t7_257.c`, `tests/unit/cmake/eng/t7_257.cmake`, `quality/evidence/T7.257.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-57093ce27e4aabc5 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.257 --regex '^test_t7_257$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.257`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.258: constVariablePointer in go2rtc_base_path.c +- **depends_on**: [T7, T13, T7.231] +- **agent_type**: worker +- **ownership**: `src/utils/go2rtc_base_path.c`, `tests/unit/test_t7_258.c`, `tests/unit/cmake/eng/t7_258.cmake`, `quality/evidence/T7.258.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-4dc0544d99c6a120 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.258 --regex '^test_t7_258$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.258`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.259: constVariablePointer in detection_model.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/video/detection_model.c`, `tests/unit/test_t7_259.c`, `tests/unit/cmake/eng/t7_259.cmake`, `quality/evidence/T7.259.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-aa0082260172b3b9 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.259 --regex '^test_t7_259$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.259`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.260: constVariablePointer in go2rtc_integration.c, go2rtc_process.c +- **depends_on**: [T7, T13, T7.214, T7.145] +- **agent_type**: worker +- **ownership**: `src/video/go2rtc/go2rtc_integration.c`, `src/video/go2rtc/go2rtc_process.c`, `tests/unit/test_t7_260.c`, `tests/unit/cmake/eng/t7_260.cmake`, `quality/evidence/T7.260.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-5f736ad7b9095aea, CPPCHECK-BUCKET-eabea03354c7b4b7 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.260 --regex '^test_t7_260$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.260`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.261: constVariablePointer in mp4_recording_core.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/video/mp4_recording_core.c`, `tests/unit/test_t7_261.c`, `tests/unit/cmake/eng/t7_261.cmake`, `quality/evidence/T7.261.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-e9a38e72fa2e4a7c +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.261 --regex '^test_t7_261$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.261`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.262: constVariablePointer in mp4_recording_writer.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/video/mp4_recording_writer.c`, `tests/unit/test_t7_262.c`, `tests/unit/cmake/eng/t7_262.cmake`, `quality/evidence/T7.262.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-3ec7089cfaa16e94 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.262 --regex '^test_t7_262$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.262`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.263: constVariablePointer in stream_state_adapter.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/video/stream_state_adapter.c`, `tests/unit/test_t7_263.c`, `tests/unit/cmake/eng/t7_263.cmake`, `quality/evidence/T7.263.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-4ffd5c5d72b2c6a0 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.263 --regex '^test_t7_263$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.263`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.264: constVariablePointer in api_handlers_go2rtc_proxy.c +- **depends_on**: [T7, T13, T7.219] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_go2rtc_proxy.c`, `tests/unit/test_t7_264.c`, `tests/unit/cmake/eng/t7_264.cmake`, `quality/evidence/T7.264.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-f4469b09cc19dae9 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.264 --regex '^test_t7_264$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.264`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.265: constVariablePointer in api_handlers_recordings_list.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_recordings_list.c`, `tests/unit/test_t7_265.c`, `tests/unit/cmake/eng/t7_265.cmake`, `quality/evidence/T7.265.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-611000c5c122ad1e +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.265 --regex '^test_t7_265$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.265`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.266: constVariablePointer in api_handlers_recordings_thumbnail.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_recordings_thumbnail.c`, `tests/unit/test_t7_266.c`, `tests/unit/cmake/eng/t7_266.cmake`, `quality/evidence/T7.266.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-d41611e4fb1c1abd +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.266 --regex '^test_t7_266$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.266`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.267: constVariablePointer in api_handlers_system_logs.c +- **depends_on**: [T7, T13, T7.149] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_system_logs.c`, `tests/unit/test_t7_267.c`, `tests/unit/cmake/eng/t7_267.cmake`, `quality/evidence/T7.267.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-b14c25475c91b307 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.267 --regex '^test_t7_267$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.267`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.268: constVariablePointer in api_handlers_timeline.c +- **depends_on**: [T7, T13, T7.107] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_timeline.c`, `tests/unit/test_t7_268.c`, `tests/unit/cmake/eng/t7_268.cmake`, `quality/evidence/T7.268.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-03f92e362a140589 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.268 --regex '^test_t7_268$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.268`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.269: constVariablePointer in api_handlers_users_backend_agnostic.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_users_backend_agnostic.c`, `tests/unit/test_t7_269.c`, `tests/unit/cmake/eng/t7_269.cmake`, `quality/evidence/T7.269.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-d45c6fa42b05db96 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.269 --regex '^test_t7_269$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.269`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.270: constVariablePointer in httpd_utils.c +- **depends_on**: [T7, T13, T7.133] +- **agent_type**: worker +- **ownership**: `src/web/httpd_utils.c`, `tests/unit/test_t7_270.c`, `tests/unit/cmake/eng/t7_270.cmake`, `quality/evidence/T7.270.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-9551a431943d8273 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.270 --regex '^test_t7_270$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.270`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.271: duplicateAssignExpression in sod.c +- **depends_on**: [T7, T13] +- **agent_type**: local +- **ownership**: `quality/reviews/t7_271-upstream.json`, `quality/evidence/T7.271.md`. +- **priority**: info +- **description**: Review pinned upstream source and reproduce the listed diagnostics in the actual linked feature profile. Record precise upstream fix/version or evidence-backed false-positive analysis. Do not edit system headers or vendored/submodule source as an untracked patch. If an update/local patch is needed, append an exact bounded integration child, dependency and regression command to T33 before closing this task. Missing upstream or feature evidence blocks completion. +- **findings**: CPPCHECK-BUCKET-86335be98f6f2e90 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 -m json.tool quality/reviews/t7_271-upstream.json` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.271`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.272: duplicateCondition in motion_detection.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/video/motion_detection.c`, `tests/unit/test_t7_272.c`, `tests/unit/cmake/eng/t7_272.cmake`, `quality/evidence/T7.272.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-f814692d9029a8a1 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.272 --regex '^test_t7_272$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.272`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.273: js/unused-local-variable in LanguageSelector.jsx +- **depends_on**: [T7, T10, T7.196] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/common/LanguageSelector.jsx`, `web/tests/remediation/t7_273.spec.jsx`, `quality/evidence/T7.273.md`. +- **priority**: info +- **description**: Reproduce the listed diagnostics and fix this accessibility/quality cause only in the assigned component cluster. Preserve localization, event callbacks, DOM contracts and extension-sensitive test behavior. Narrow an untestable lint-only case with documented static evidence; behavioral accessibility changes require interaction tests. +- **findings**: CODEQUALITY-941 +- **acceptance_criteria**: + - Remove only the proven unused binding; assert language selection/persistence and localized rendering still work. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `npm --prefix web test -- --runInBand --testPathPatterns=t7_273` + - `mkdir -p build/eng-artifacts/T7.273` + - `web/node_modules/.bin/eslint web/js/components/preact/common/LanguageSelector.jsx --format json --output-file build/eng-artifacts/T7.273/eslint.json` + - `python3 -c 'import json; data=json.load(open('"'"'build/eng-artifacts/T7.273/eslint.json'"'"')); assert not [m for f in data for m in f["messages"] if m.get("ruleId")=='"'"'js/unused-local-variable'"'"'], "targeted diagnostic remains"'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.273`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.274: knownConditionTrueFalse in sod_img_reader.h +- **depends_on**: [T7, T13] +- **agent_type**: local +- **ownership**: `quality/reviews/t7_274-upstream.json`, `quality/evidence/T7.274.md`. +- **priority**: info +- **description**: Review pinned upstream source and reproduce the listed diagnostics in the actual linked feature profile. Record precise upstream fix/version or evidence-backed false-positive analysis. Do not edit system headers or vendored/submodule source as an untracked patch. If an update/local patch is needed, append an exact bounded integration child, dependency and regression command to T33 before closing this task. Missing upstream or feature evidence blocks completion. +- **findings**: CPPCHECK-BUCKET-1f586f8741e770e9 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 -m json.tool quality/reviews/t7_274-upstream.json` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.274`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.275: knownConditionTrueFalse in event_router.c +- **depends_on**: [T7, T13, T7.90] +- **agent_type**: worker +- **ownership**: `src/core/event_router.c`, `tests/unit/test_t7_275.c`, `tests/unit/cmake/eng/t7_275.cmake`, `quality/evidence/T7.275.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-95058fe138843b1f +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.275 --regex '^test_t7_275$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.275`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.276: knownConditionTrueFalse in mqtt_event_adapter.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/core/mqtt_event_adapter.c`, `tests/unit/test_t7_276.c`, `tests/unit/cmake/eng/t7_276.cmake`, `quality/evidence/T7.276.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-9c71189558451cd4 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.276 --regex '^test_t7_276$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.276`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.277: knownConditionTrueFalse in db_storage_targets.c +- **depends_on**: [T7, T13, T7.136] +- **agent_type**: worker +- **ownership**: `src/database/db_storage_targets.c`, `tests/unit/test_t7_277.c`, `tests/unit/cmake/eng/t7_277.cmake`, `quality/evidence/T7.277.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-2f8dc61aeb7cdda6 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.277 --regex '^test_t7_277$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.277`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.278: knownConditionTrueFalse in ezxml.c +- **depends_on**: [T7, T13] +- **agent_type**: local +- **ownership**: `quality/reviews/t7_278-upstream.json`, `quality/evidence/T7.278.md`. +- **priority**: info +- **description**: Review pinned upstream source and reproduce the listed diagnostics in the actual linked feature profile. Record precise upstream fix/version or evidence-backed false-positive analysis. Do not edit system headers or vendored/submodule source as an untracked patch. If an update/local patch is needed, append an exact bounded integration child, dependency and regression command to T33 before closing this task. Missing upstream or feature evidence blocks completion. +- **findings**: CPPCHECK-BUCKET-c7e9176ca688d280 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 -m json.tool quality/reviews/t7_278-upstream.json` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.278`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.279: knownConditionTrueFalse in sod.c +- **depends_on**: [T7, T13] +- **agent_type**: local +- **ownership**: `quality/reviews/t7_279-upstream.json`, `quality/evidence/T7.279.md`. +- **priority**: info +- **description**: Review pinned upstream source and reproduce the listed diagnostics in the actual linked feature profile. Record precise upstream fix/version or evidence-backed false-positive analysis. Do not edit system headers or vendored/submodule source as an untracked patch. If an update/local patch is needed, append an exact bounded integration child, dependency and regression command to T33 before closing this task. Missing upstream or feature evidence blocks completion. +- **findings**: CPPCHECK-BUCKET-064299ecf7d09b55 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 -m json.tool quality/reviews/t7_279-upstream.json` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.279`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.280: knownConditionTrueFalse in linux_process.c +- **depends_on**: [T7, T13, T7.254] +- **agent_type**: worker +- **ownership**: `src/telemetry/collectors/linux_process.c`, `tests/unit/test_t7_280.c`, `tests/unit/cmake/eng/t7_280.cmake`, `quality/evidence/T7.280.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-28b7ea4b18c686f4 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.280 --regex '^test_t7_280$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.280`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.281: knownConditionTrueFalse in health_helper_runner.c +- **depends_on**: [T7, T13, T7.68] +- **agent_type**: worker +- **ownership**: `src/telemetry/health_helper_runner.c`, `tests/unit/test_t7_281.c`, `tests/unit/cmake/eng/t7_281.cmake`, `quality/evidence/T7.281.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-1ac7ae81e97d3df2 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.281 --regex '^test_t7_281$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.281`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.282: knownConditionTrueFalse in api_handlers_imaging.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_imaging.c`, `tests/unit/test_t7_282.c`, `tests/unit/cmake/eng/t7_282.cmake`, `quality/evidence/T7.282.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-0251d17e5b89df2a +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.282 --regex '^test_t7_282$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.282`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.283: redundantCondition in sod_img_reader.h +- **depends_on**: [T7, T13] +- **agent_type**: local +- **ownership**: `quality/reviews/t7_283-upstream.json`, `quality/evidence/T7.283.md`. +- **priority**: info +- **description**: Review pinned upstream source and reproduce the listed diagnostics in the actual linked feature profile. Record precise upstream fix/version or evidence-backed false-positive analysis. Do not edit system headers or vendored/submodule source as an untracked patch. If an update/local patch is needed, append an exact bounded integration child, dependency and regression command to T33 before closing this task. Missing upstream or feature evidence blocks completion. +- **findings**: CPPCHECK-BUCKET-eee46ed30daef891 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 -m json.tool quality/reviews/t7_283-upstream.json` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.283`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.284: redundantInitialization in sod_img_reader.h +- **depends_on**: [T7, T13] +- **agent_type**: local +- **ownership**: `quality/reviews/t7_284-upstream.json`, `quality/evidence/T7.284.md`. +- **priority**: info +- **description**: Review pinned upstream source and reproduce the listed diagnostics in the actual linked feature profile. Record precise upstream fix/version or evidence-backed false-positive analysis. Do not edit system headers or vendored/submodule source as an untracked patch. If an update/local patch is needed, append an exact bounded integration child, dependency and regression command to T33 before closing this task. Missing upstream or feature evidence blocks completion. +- **findings**: CPPCHECK-BUCKET-dcdbd8e6357d7e12 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 -m json.tool quality/reviews/t7_284-upstream.json` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.284`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.285: redundantInitialization in db_authorization.c +- **depends_on**: [T7, T13, T7.245] +- **agent_type**: worker +- **ownership**: `src/database/db_authorization.c`, `tests/unit/test_t7_285.c`, `tests/unit/cmake/eng/t7_285.cmake`, `quality/evidence/T7.285.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-bbeef7abfa51d073 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.285 --regex '^test_t7_285$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.285`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.286: redundantInitialization in recording_path.c +- **depends_on**: [T7, T13, T7.157, T27.1] +- **agent_type**: worker +- **ownership**: `src/video/recording_path.c`, `tests/unit/test_t7_286.c`, `tests/unit/cmake/eng/t7_286.cmake`, `quality/evidence/T7.286.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-0be349a4e3fc0518 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.286 --regex '^test_t7_286$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.286`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.287: redundantPointerOp in sod.c +- **depends_on**: [T7, T13] +- **agent_type**: local +- **ownership**: `quality/reviews/t7_287-upstream.json`, `quality/evidence/T7.287.md`. +- **priority**: info +- **description**: Review pinned upstream source and reproduce the listed diagnostics in the actual linked feature profile. Record precise upstream fix/version or evidence-backed false-positive analysis. Do not edit system headers or vendored/submodule source as an untracked patch. If an update/local patch is needed, append an exact bounded integration child, dependency and regression command to T33 before closing this task. Missing upstream or feature evidence blocks completion. +- **findings**: CPPCHECK-BUCKET-8f929a70e127f155 +- **acceptance_criteria**: + - Reproduce each reported null/empty/one-past-boundary or incompatible-pointer path under ASan/UBSan; assert no invalid dereference/read/write and correct valid-path result, terminator and ownership cleanup. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 -m json.tool quality/reviews/t7_287-upstream.json` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.287`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.288: shadowFunction in db_storage_targets.c +- **depends_on**: [T7, T13, T7.277] +- **agent_type**: worker +- **ownership**: `src/database/db_storage_targets.c`, `tests/unit/test_t7_288.c`, `tests/unit/cmake/eng/t7_288.cmake`, `quality/evidence/T7.288.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-147fba26f7ae351b +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.288 --regex '^test_t7_288$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.288`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.289: shadowFunction in stream_manager.c +- **depends_on**: [T7, T13, T7.62] +- **agent_type**: worker +- **ownership**: `src/video/stream_manager.c`, `tests/unit/test_t7_289.c`, `tests/unit/cmake/eng/t7_289.cmake`, `quality/evidence/T7.289.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-05b22ae697eaa4ca +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.289 --regex '^test_t7_289$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.289`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.290: shadowVariable in config.h +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `include/core/config.h`, `tests/unit/test_t7_290.c`, `tests/unit/cmake/eng/t7_290.cmake`, `quality/evidence/T7.290.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-9d2c058ee958f85a +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.290 --regex '^test_t7_290$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.290`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.291: unpreciseMathCall in sod.c +- **depends_on**: [T7, T13] +- **agent_type**: local +- **ownership**: `quality/reviews/t7_291-upstream.json`, `quality/evidence/T7.291.md`. +- **priority**: info +- **description**: Review pinned upstream source and reproduce the listed diagnostics in the actual linked feature profile. Record precise upstream fix/version or evidence-backed false-positive analysis. Do not edit system headers or vendored/submodule source as an untracked patch. If an update/local patch is needed, append an exact bounded integration child, dependency and regression command to T33 before closing this task. Missing upstream or feature evidence blocks completion. +- **findings**: CPPCHECK-BUCKET-426773bc9bc5a5d5 +- **acceptance_criteria**: + - Exercise signed/unsigned boundaries, negative/zero values, maximum accepted values and multiplication overflow before conversion; assert valid results are unchanged and invalid values cannot wrap, divide by zero or allocate oversized memory. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 -m json.tool quality/reviews/t7_291-upstream.json` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.291`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.292: unreadVariable in sod_img_reader.h +- **depends_on**: [T7, T13] +- **agent_type**: local +- **ownership**: `quality/reviews/t7_292-upstream.json`, `quality/evidence/T7.292.md`. +- **priority**: info +- **description**: Review pinned upstream source and reproduce the listed diagnostics in the actual linked feature profile. Record precise upstream fix/version or evidence-backed false-positive analysis. Do not edit system headers or vendored/submodule source as an untracked patch. If an update/local patch is needed, append an exact bounded integration child, dependency and regression command to T33 before closing this task. Missing upstream or feature evidence blocks completion. +- **findings**: CPPCHECK-BUCKET-eda9b7001501e847 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 -m json.tool quality/reviews/t7_292-upstream.json` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.292`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.293: unreadVariable in mqtt_client.c +- **depends_on**: [T7, T13, T7.29] +- **agent_type**: worker +- **ownership**: `src/core/mqtt_client.c`, `tests/unit/test_t7_293.c`, `tests/unit/cmake/eng/t7_293.cmake`, `quality/evidence/T7.293.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-779ca0994d939da1 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.293 --regex '^test_t7_293$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.293`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.294: unreadVariable in sod.c +- **depends_on**: [T7, T13] +- **agent_type**: local +- **ownership**: `quality/reviews/t7_294-upstream.json`, `quality/evidence/T7.294.md`. +- **priority**: info +- **description**: Review pinned upstream source and reproduce the listed diagnostics in the actual linked feature profile. Record precise upstream fix/version or evidence-backed false-positive analysis. Do not edit system headers or vendored/submodule source as an untracked patch. If an update/local patch is needed, append an exact bounded integration child, dependency and regression command to T33 before closing this task. Missing upstream or feature evidence blocks completion. +- **findings**: CPPCHECK-BUCKET-93112ea9a6a3c10d +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 -m json.tool quality/reviews/t7_294-upstream.json` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.294`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.295: unreadVariable in onvif_ptz.c +- **depends_on**: [T7, T13, T7.218] +- **agent_type**: worker +- **ownership**: `src/video/onvif_ptz.c`, `tests/unit/test_t7_295.c`, `tests/unit/cmake/eng/t7_295.cmake`, `quality/evidence/T7.295.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-b1b17a4b9a2646a0 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.295 --regex '^test_t7_295$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.295`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.296: unsignedLessThanZero in sod.c +- **depends_on**: [T7, T13] +- **agent_type**: local +- **ownership**: `quality/reviews/t7_296-upstream.json`, `quality/evidence/T7.296.md`. +- **priority**: info +- **description**: Review pinned upstream source and reproduce the listed diagnostics in the actual linked feature profile. Record precise upstream fix/version or evidence-backed false-positive analysis. Do not edit system headers or vendored/submodule source as an untracked patch. If an update/local patch is needed, append an exact bounded integration child, dependency and regression command to T33 before closing this task. Missing upstream or feature evidence blocks completion. +- **findings**: CPPCHECK-BUCKET-a4ae78869ec196b6 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 -m json.tool quality/reviews/t7_296-upstream.json` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.296`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.297: unusedStructMember in mqtt_destination_client.c +- **depends_on**: [T7, T13, T7.224] +- **agent_type**: worker +- **ownership**: `src/core/mqtt_destination_client.c`, `tests/unit/test_t7_297.c`, `tests/unit/cmake/eng/t7_297.cmake`, `quality/evidence/T7.297.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-efd7988ca87c95ed +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.297 --regex '^test_t7_297$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.297`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.298: unusedStructMember in sod.c +- **depends_on**: [T7, T13] +- **agent_type**: local +- **ownership**: `quality/reviews/t7_298-upstream.json`, `quality/evidence/T7.298.md`. +- **priority**: info +- **description**: Review pinned upstream source and reproduce the listed diagnostics in the actual linked feature profile. Record precise upstream fix/version or evidence-backed false-positive analysis. Do not edit system headers or vendored/submodule source as an untracked patch. If an update/local patch is needed, append an exact bounded integration child, dependency and regression command to T33 before closing this task. Missing upstream or feature evidence blocks completion. +- **findings**: CPPCHECK-BUCKET-49117790281d294c +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 -m json.tool quality/reviews/t7_298-upstream.json` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.298`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.299: unusedStructMember in sod_detection.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/video/sod_detection.c`, `tests/unit/test_t7_299.c`, `tests/unit/cmake/eng/t7_299.cmake`, `quality/evidence/T7.299.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-a12a87ba590d2536 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.299 --regex '^test_t7_299$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.299`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.300: uselessAssignmentArg in sod.c +- **depends_on**: [T7, T13] +- **agent_type**: local +- **ownership**: `quality/reviews/t7_300-upstream.json`, `quality/evidence/T7.300.md`. +- **priority**: info +- **description**: Review pinned upstream source and reproduce the listed diagnostics in the actual linked feature profile. Record precise upstream fix/version or evidence-backed false-positive analysis. Do not edit system headers or vendored/submodule source as an untracked patch. If an update/local patch is needed, append an exact bounded integration child, dependency and regression command to T33 before closing this task. Missing upstream or feature evidence blocks completion. +- **findings**: CPPCHECK-BUCKET-56c122b53a2e50cb +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 -m json.tool quality/reviews/t7_300-upstream.json` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.300`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.301: variableScope in sod_img_reader.h, sod_img_writer.h +- **depends_on**: [T7, T13] +- **agent_type**: local +- **ownership**: `quality/reviews/t7_301-upstream.json`, `quality/evidence/T7.301.md`. +- **priority**: info +- **description**: Review pinned upstream source and reproduce the listed diagnostics in the actual linked feature profile. Record precise upstream fix/version or evidence-backed false-positive analysis. Do not edit system headers or vendored/submodule source as an untracked patch. If an update/local patch is needed, append an exact bounded integration child, dependency and regression command to T33 before closing this task. Missing upstream or feature evidence blocks completion. +- **findings**: CPPCHECK-BUCKET-ae4dbf08b9cfd1fe, CPPCHECK-BUCKET-b9ff6009cfa26f69 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 -m json.tool quality/reviews/t7_301-upstream.json` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.301`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.302: variableScope in db_detection_engines.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/database/db_detection_engines.c`, `tests/unit/test_t7_302.c`, `tests/unit/cmake/eng/t7_302.cmake`, `quality/evidence/T7.302.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-b614f3b94230fe9a +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.302 --regex '^test_t7_302$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.302`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.303: variableScope in db_lpr_reads.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/database/db_lpr_reads.c`, `tests/unit/test_t7_303.c`, `tests/unit/cmake/eng/t7_303.cmake`, `quality/evidence/T7.303.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-664e70c550c3ef36 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.303 --regex '^test_t7_303$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.303`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.304: variableScope in db_recordings.c +- **depends_on**: [T7, T13, T29.2] +- **agent_type**: worker +- **ownership**: `src/database/db_recordings.c`, `tests/unit/test_t7_304.c`, `tests/unit/cmake/eng/t7_304.cmake`, `quality/evidence/T7.304.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-2afdb0b1f1146eeb +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.304 --regex '^test_t7_304$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.304`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.305: variableScope in ezxml.c +- **depends_on**: [T7, T13] +- **agent_type**: local +- **ownership**: `quality/reviews/t7_305-upstream.json`, `quality/evidence/T7.305.md`. +- **priority**: info +- **description**: Review pinned upstream source and reproduce the listed diagnostics in the actual linked feature profile. Record precise upstream fix/version or evidence-backed false-positive analysis. Do not edit system headers or vendored/submodule source as an untracked patch. If an update/local patch is needed, append an exact bounded integration child, dependency and regression command to T33 before closing this task. Missing upstream or feature evidence blocks completion. +- **findings**: CPPCHECK-BUCKET-96d5b13210206feb +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 -m json.tool quality/reviews/t7_305-upstream.json` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.305`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.306: variableScope in sod.c +- **depends_on**: [T7, T13] +- **agent_type**: local +- **ownership**: `quality/reviews/t7_306-upstream.json`, `quality/evidence/T7.306.md`. +- **priority**: info +- **description**: Review pinned upstream source and reproduce the listed diagnostics in the actual linked feature profile. Record precise upstream fix/version or evidence-backed false-positive analysis. Do not edit system headers or vendored/submodule source as an untracked patch. If an update/local patch is needed, append an exact bounded integration child, dependency and regression command to T33 before closing this task. Missing upstream or feature evidence blocks completion. +- **findings**: CPPCHECK-BUCKET-50bc6e286ac69abd +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 -m json.tool quality/reviews/t7_306-upstream.json` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.306`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.307: variableScope in storage_deletion.c +- **depends_on**: [T7, T13, T7.252, T28.1, T29.3] +- **agent_type**: worker +- **ownership**: `src/storage/storage_deletion.c`, `tests/unit/test_t7_307.c`, `tests/unit/cmake/eng/t7_307.cmake`, `quality/evidence/T7.307.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-89a47a59067748c1 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.307 --regex '^test_t7_307$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.307`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.308: variableScope in storage_migration.c +- **depends_on**: [T7, T13, T7.211] +- **agent_type**: worker +- **ownership**: `src/storage/storage_migration.c`, `tests/unit/test_t7_308.c`, `tests/unit/cmake/eng/t7_308.cmake`, `quality/evidence/T7.308.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-da5006735d5c5165 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.308 --regex '^test_t7_308$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.308`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.309: variableScope in storage_placement.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/storage/storage_placement.c`, `tests/unit/test_t7_309.c`, `tests/unit/cmake/eng/t7_309.cmake`, `quality/evidence/T7.309.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-51dd1a644803f48d +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.309 --regex '^test_t7_309$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.309`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.310: variableScope in storage_s3.c +- **depends_on**: [T7, T13, T7.142] +- **agent_type**: worker +- **ownership**: `src/storage/storage_s3.c`, `tests/unit/test_t7_310.c`, `tests/unit/cmake/eng/t7_310.cmake`, `quality/evidence/T7.310.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-a3e8d56a93d61112 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.310 --regex '^test_t7_310$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.310`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.311: variableScope in linux_cgroup.c, linux_thermal.c +- **depends_on**: [T7, T13, T7.228, T7.254] +- **agent_type**: worker +- **ownership**: `src/telemetry/collectors/linux_cgroup.c`, `src/telemetry/collectors/linux_thermal.c`, `tests/unit/test_t7_311.c`, `tests/unit/cmake/eng/t7_311.cmake`, `quality/evidence/T7.311.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-771b3fde1937be8a, CPPCHECK-BUCKET-e4dae70b5d1bd770 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.311 --regex '^test_t7_311$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.311`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.312: variableScope in stream_metrics.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/telemetry/stream_metrics.c`, `tests/unit/test_t7_312.c`, `tests/unit/cmake/eng/t7_312.cmake`, `quality/evidence/T7.312.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-9cc73ce911540ad4 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.312 --regex '^test_t7_312$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.312`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.313: variableScope in api_detection.c +- **depends_on**: [T7, T13, T7.213] +- **agent_type**: worker +- **ownership**: `src/video/api_detection.c`, `tests/unit/test_t7_313.c`, `tests/unit/cmake/eng/t7_313.cmake`, `quality/evidence/T7.313.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-905555e786df0459 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.313 --regex '^test_t7_313$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.313`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.314: variableScope in hls_directory.c +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/video/hls/hls_directory.c`, `tests/unit/test_t7_314.c`, `tests/unit/cmake/eng/t7_314.cmake`, `quality/evidence/T7.314.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-5d0aacd6263110c5 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.314 --regex '^test_t7_314$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.314`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.315: variableScope in recording_transcode.c +- **depends_on**: [T7, T13, T7.66] +- **agent_type**: worker +- **ownership**: `src/video/recording_transcode.c`, `tests/unit/test_t7_315.c`, `tests/unit/cmake/eng/t7_315.cmake`, `quality/evidence/T7.315.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-ec17ee32f6d27cdb +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.315 --regex '^test_t7_315$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.315`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.316: variableScope in unified_detection_thread.c +- **depends_on**: [T7, T13, T7.235] +- **agent_type**: worker +- **ownership**: `src/video/unified_detection_thread.c`, `tests/unit/test_t7_316.c`, `tests/unit/cmake/eng/t7_316.cmake`, `quality/evidence/T7.316.md`. +- **priority**: info +- **description**: Reproduce each listed diagnostic from its raw T1 source report and trace its caller/ownership before changing code. Fix the bounded cause, write focused native regressions and register the dedicated test through the T13 eng fragment seam. These diagnostics are hypotheses until reproduced: if sites have distinct root causes, split explicit child tasks with exact ownership and add them to T33; no catch-all rewrite or blanket suppression. +- **findings**: CPPCHECK-BUCKET-d4393cc822a0c2d3 +- **acceptance_criteria**: + - Inspect the exact reported expression and caller invariants. Add a focused before/after behavioral assertion for the affected branch or document why the change is semantic-preserving and verify compiler/scanner diagnostics disappear. Never delete side effects, suppress a rule wholesale or claim a warning count reduction proves correctness. + - Each mapped finding has individual fixed-with-regression evidence or an explicitly approved, current, narrowly scoped exception; unobserved/excluded diagnostics are not fixed. + - Preserve unrelated baseline diagnostics. Final full-profile fresh reports and optional feature coverage are reconciled in T33. +- **validation**: + - `python3 scripts/quality/run-native.py --task T7.316 --regex '^test_t7_316$'` + - Reproducer locations and scanner fingerprints: `quality/remediation-map.json` task `T7.316`; inspect the raw T1 report and trace the caller. +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.317: Authenticate before exposing stream existence +- **depends_on**: [T7, T11, T13, T7.64, T7.50, T13.3, T13.4] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_streams_get.c`, `src/web/api_handlers_streams_modify.c`, `tests/integration/specs/t7_317.api.spec.ts`, `quality/evidence/T7.317.md`. +- **priority**: medium +- **description**: Reproduce the T11 candidate against disposable synthetic fixtures and real C handlers before implementation. Add an independent response/state/side-effect regression, then implement the narrow correction. Coordinate schema and generated changes through the separate reconciliation task; do not weaken the contract to conceal a defect. +- **findings**: F-T11-01 +- **acceptance_criteria**: + - Unauthenticated known and unknown names both return the authentication failure; authenticated unauthorized users retain intentional policy response without leaking privileged data. + - Record the before/after behavior and exact failing-to-passing command; use synthetic credentials and disposable storage. +- **validation**: + - `node scripts/quality/run-integration.mjs --task T7.317 --project api --grep @t7_317` + - `npm run typecheck:tests` +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.318: Correct documented Streams wire examples +- **depends_on**: [T7, T11, T7.319, T7.320, T7.321, T7.322, T7.323, T7.324, T7.325, T7.326, T7.327, T7.328, T7.329, T7.330, T7.331, T7.332, T7.333] +- **agent_type**: worker +- **ownership**: `docs/API.md`, `quality/evidence/T7.318.md`. +- **priority**: medium +- **description**: Reproduce the T11 candidate against disposable synthetic fixtures and real C handlers before implementation. Update documentation only after the wire behavior is reconciled. +- **findings**: F-T11-02 +- **acceptance_criteria**: + - Examples use the real array/page envelopes, integer percentage threshold and emitted names/statuses; every example maps to the revised contract. + - Record the before/after behavior and exact failing-to-passing command; use synthetic credentials and disposable storage. +- **validation**: + - `python3 scripts/check_docs_links.py` +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.319: Remove query credentials from viewer URLs +- **depends_on**: [T7, T11, T13, T7.317] +- **agent_type**: worker +- **ownership**: `src/core/url_utils.c`, `src/web/api_handlers_streams_get.c`, `tests/integration/specs/t7_319.api.spec.ts`, `quality/evidence/T7.319.md`. +- **priority**: high +- **description**: Reproduce the T11 candidate against disposable synthetic fixtures and real C handlers before implementation. Add an independent response/state/side-effect regression, then implement the narrow correction. Coordinate schema and generated changes through the separate reconciliation task; do not weaken the contract to conceal a defect. +- **findings**: F-T11-03 +- **acceptance_criteria**: + - Viewer responses contain neither userinfo nor sensitive query credentials, including encoded/mixed-case keys; configurers retain required edit data and benign query options remain usable. + - Record the before/after behavior and exact failing-to-passing command; use synthetic credentials and disposable storage. +- **validation**: + - `node scripts/quality/run-integration.mjs --task T7.319 --project api --grep @t7_319` + - `npm run typecheck:tests` +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.320: Reject non-object stream updates +- **depends_on**: [T7, T11, T13, T7.317, T13.3, T13.4] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_streams_modify.c`, `tests/integration/specs/t7_320.api.spec.ts`, `quality/evidence/T7.320.md`. +- **priority**: medium +- **description**: Reproduce the T11 candidate against disposable synthetic fixtures and real C handlers before implementation. Add an independent response/state/side-effect regression, then implement the narrow correction. Coordinate schema and generated changes through the separate reconciliation task; do not weaken the contract to conceal a defect. +- **findings**: F-T11-04 +- **acceptance_criteria**: + - null, arrays and primitives return 400 with no mutation, ONVIF request or reload; an empty object has explicit unchanged semantics. + - Record the before/after behavior and exact failing-to-passing command; use synthetic credentials and disposable storage. +- **validation**: + - `node scripts/quality/run-integration.mjs --task T7.320 --project api --grep @t7_320` + - `npm run typecheck:tests` +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.321: Reject wrong-typed stream fields +- **depends_on**: [T7, T11, T13, T7.320, T13.3, T13.4] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_streams_modify.c`, `tests/integration/specs/t7_321.api.spec.ts`, `quality/evidence/T7.321.md`. +- **priority**: medium +- **description**: Reproduce the T11 candidate against disposable synthetic fixtures and real C handlers before implementation. Add an independent response/state/side-effect regression, then implement the narrow correction. Coordinate schema and generated changes through the separate reconciliation task; do not weaken the contract to conceal a defect. +- **findings**: F-T11-05 +- **acceptance_criteria**: + - Wrong types return 400 with state unchanged; supported nullable clears retain their documented semantics and validation precedes all side effects. + - Record the before/after behavior and exact failing-to-passing command; use synthetic credentials and disposable storage. +- **validation**: + - `node scripts/quality/run-integration.mjs --task T7.321 --project api --grep @t7_321` + - `npm run typecheck:tests` +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.322: Validate bounded stream numeric fields +- **depends_on**: [T7, T11, T13, T7.321, T13.3, T13.4] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_streams_modify.c`, `tests/integration/specs/t7_322.api.spec.ts`, `quality/evidence/T7.322.md`. +- **priority**: medium +- **description**: Reproduce the T11 candidate against disposable synthetic fixtures and real C handlers before implementation. Add an independent response/state/side-effect regression, then implement the narrow correction. Coordinate schema and generated changes through the separate reconciliation task; do not weaken the contract to conceal a defect. +- **findings**: F-T11-06 +- **acceptance_criteria**: + - Protocol enum, priorities, interval, buffer, storage, PTZ and port limits reject out-of-range and fractional values before mutation; boundaries agree with storage and runtime callers. + - Record the before/after behavior and exact failing-to-passing command; use synthetic credentials and disposable storage. +- **validation**: + - `node scripts/quality/run-integration.mjs --task T7.322 --project api --grep @t7_322` + - `npm run typecheck:tests` +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.323: Validate complete boolean schedules +- **depends_on**: [T7, T11, T13, T7.322, T13.3, T13.4] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_streams_modify.c`, `tests/integration/specs/t7_323.api.spec.ts`, `quality/evidence/T7.323.md`. +- **priority**: medium +- **description**: Reproduce the T11 candidate against disposable synthetic fixtures and real C handlers before implementation. Add an independent response/state/side-effect regression, then implement the narrow correction. Coordinate schema and generated changes through the separate reconciliation task; do not weaken the contract to conceal a defect. +- **findings**: F-T11-07 +- **acceptance_criteria**: + - Only exactly 168 booleans are accepted; short/long/non-array or wrong element types fail atomically and valid week schedules round-trip unchanged. + - Record the before/after behavior and exact failing-to-passing command; use synthetic credentials and disposable storage. +- **validation**: + - `node scripts/quality/run-integration.mjs --task T7.323 --project api --grep @t7_323` + - `npm run typecheck:tests` +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.324: Address encoded slash names without aliasing +- **depends_on**: [T7, T11, T13, T7.319] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_streams_get.c`, `tests/integration/specs/t7_324.api.spec.ts`, `quality/evidence/T7.324.md`. +- **priority**: medium +- **description**: Reproduce the T11 candidate against disposable synthetic fixtures and real C handlers before implementation. Add an independent response/state/side-effect regression, then implement the narrow correction. Coordinate schema and generated changes through the separate reconciliation task; do not weaken the contract to conceal a defect. +- **findings**: F-T11-08 +- **acceptance_criteria**: + - An encoded slash may either address the complete supported legacy name or be explicitly rejected; it must never select/authorize a different truncated camera. + - Record the before/after behavior and exact failing-to-passing command; use synthetic credentials and disposable storage. +- **validation**: + - `node scripts/quality/run-integration.mjs --task T7.324 --project api --grep @t7_324` + - `npm run typecheck:tests` +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.325: Enforce decoded stream name limits consistently +- **depends_on**: [T7, T11, T13, T7.324] +- **agent_type**: worker +- **ownership**: `src/web/request_response.c`, `src/web/api_handlers_streams_get.c`, `tests/integration/specs/t7_325.api.spec.ts`, `quality/evidence/T7.325.md`. +- **priority**: medium +- **description**: Reproduce the T11 candidate against disposable synthetic fixtures and real C handlers before implementation. Add an independent response/state/side-effect regression, then implement the narrow correction. Coordinate schema and generated changes through the separate reconciliation task; do not weaken the contract to conceal a defect. +- **findings**: F-T11-09 +- **acceptance_criteria**: + - UTF-8 names at the stored byte limit are consistently addressable by PUT/full/zones; encoded suffix length does not truncate/alias a name. Malformed escapes/NUL/dot segments remain rejected. Current encoded suffix includes /full or /zones. + - Record the before/after behavior and exact failing-to-passing command; use synthetic credentials and disposable storage. +- **validation**: + - `node scripts/quality/run-integration.mjs --task T7.325 --project api --grep @t7_325` + - `npm run typecheck:tests` +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.326: Handle missing fleet inventory rows consistently +- **depends_on**: [T7, T11, T13, T7.325] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_streams_get.c`, `tests/integration/specs/t7_326.api.spec.ts`, `quality/evidence/T7.326.md`. +- **priority**: medium +- **description**: Reproduce the T11 candidate against disposable synthetic fixtures and real C handlers before implementation. Add an independent response/state/side-effect regression, then implement the narrow correction. Coordinate schema and generated changes through the separate reconciliation task; do not weaken the contract to conceal a defect. +- **findings**: F-T11-10 +- **acceptance_criteria**: + - A missing authorization inventory row cannot expose a camera; document fail-closed behavior and isolate an inconsistent row only if policy permits, while preserving authorized list results and surfacing the inconsistency. + - Record the before/after behavior and exact failing-to-passing command; use synthetic credentials and disposable storage. +- **validation**: + - `node scripts/quality/run-integration.mjs --task T7.326 --project api --grep @t7_326` + - `npm run typecheck:tests` +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.327: Preserve omitted ONVIF update state +- **depends_on**: [T7, T11, T13, T7.323, T13.3, T13.4] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_streams_modify.c`, `tests/integration/specs/t7_327.api.spec.ts`, `quality/evidence/T7.327.md`. +- **priority**: medium +- **description**: Reproduce the T11 candidate against disposable synthetic fixtures and real C handlers before implementation. Add an independent response/state/side-effect regression, then implement the narrow correction. Coordinate schema and generated changes through the separate reconciliation task; do not weaken the contract to conceal a defect. +- **findings**: F-T11-11 +- **acceptance_criteria**: + - A tags-only or empty update preserves isOnvif and credentials without an ONVIF network call; explicit ONVIF changes retain their intended behavior. + - Record the before/after behavior and exact failing-to-passing command; use synthetic credentials and disposable storage. +- **validation**: + - `node scripts/quality/run-integration.mjs --task T7.327 --project api --grep @t7_327` + - `npm run typecheck:tests` +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.328: Make privacy mutations atomic and report failure +- **depends_on**: [T7, T11, T13, T7.327, T13.3, T13.4] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_streams_modify.c`, `tests/integration/specs/t7_328.api.spec.ts`, `quality/evidence/T7.328.md`. +- **priority**: medium +- **description**: Reproduce the T11 candidate against disposable synthetic fixtures and real C handlers before implementation. Add an independent response/state/side-effect regression, then implement the narrow correction. Coordinate schema and generated changes through the separate reconciliation task; do not weaken the contract to conceal a defect. +- **findings**: F-T11-12 +- **acceptance_criteria**: + - Privacy-only success follows a successful database write; database failure returns failure. Mixed privacy/edit requests are rejected before enable/reload/network side effects or handled through a separately specified atomic contract. + - Record the before/after behavior and exact failing-to-passing command; use synthetic credentials and disposable storage. +- **validation**: + - `node scripts/quality/run-integration.mjs --task T7.328 --project api --grep @t7_328` + - `npm run typecheck:tests` +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.329: Expose demo mode for authenticated users +- **depends_on**: [T7, T11, T13] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_auth_backend_agnostic.c`, `tests/integration/specs/t7_329.api.spec.ts`, `quality/evidence/T7.329.md`. +- **priority**: medium +- **description**: Reproduce the T11 candidate against disposable synthetic fixtures and real C handlers before implementation. Add an independent response/state/side-effect regression, then implement the narrow correction. Coordinate schema and generated changes through the separate reconciliation task; do not weaken the contract to conceal a defect. +- **findings**: F-T11-13 +- **acceptance_criteria**: + - Authenticated demo responses carry authoritative demo state while roles remain accurate, server-side redaction remains enforced and normal authenticated responses remain compatible. + - Record the before/after behavior and exact failing-to-passing command; use synthetic credentials and disposable storage. +- **validation**: + - `node scripts/quality/run-integration.mjs --task T7.329 --project api --grep @t7_329` + - `npm run typecheck:tests` +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.330: Reject overlong stream strings without truncation +- **depends_on**: [T7, T11, T13, T7.328, T13.3, T13.4] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_streams_modify.c`, `tests/integration/specs/t7_330.api.spec.ts`, `quality/evidence/T7.330.md`. +- **priority**: medium +- **description**: Reproduce the T11 candidate against disposable synthetic fixtures and real C handlers before implementation. Add an independent response/state/side-effect regression, then implement the narrow correction. Coordinate schema and generated changes through the separate reconciliation task; do not weaken the contract to conceal a defect. +- **findings**: F-T11-14 +- **acceptance_criteria**: + - Each field checks UTF-8 byte length against the destination buffer before mutation; maximum valid values round-trip and one-byte overflow is rejected atomically, including detection_model temporary-buffer limits. + - Record the before/after behavior and exact failing-to-passing command; use synthetic credentials and disposable storage. +- **validation**: + - `node scripts/quality/run-integration.mjs --task T7.330 --project api --grep @t7_330` + - `npm run typecheck:tests` +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.331: Bound ONVIF edit validation latency +- **depends_on**: [T7, T11, T13, T7.330, T13.3, T13.4] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_streams_modify.c`, `tests/integration/specs/t7_331.api.spec.ts`, `quality/evidence/T7.331.md`. +- **priority**: medium +- **description**: Reproduce the T11 candidate against disposable synthetic fixtures and real C handlers before implementation. Add an independent response/state/side-effect regression, then implement the narrow correction. Coordinate schema and generated changes through the separate reconciliation task; do not weaken the contract to conceal a defect. +- **findings**: F-T11-15 +- **acceptance_criteria**: + - An unresponsive synthetic ONVIF endpoint cannot monopolize a worker indefinitely; preserve accepted/persisted semantics and avoid blind retries of possibly successful mutations. Record the deadline before the test, below the client timeout or explicitly reconcile client behavior. + - Record the before/after behavior and exact failing-to-passing command; use synthetic credentials and disposable storage. +- **validation**: + - `node scripts/quality/run-integration.mjs --task T7.331 --project api --grep @t7_331` + - `npm run typecheck:tests` +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.332: Preserve detection threshold round trips +- **depends_on**: [T7, T11, T13, T7.326, T7.331, T13.3, T13.4] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_streams_get.c`, `src/web/api_handlers_streams_modify.c`, `tests/integration/specs/t7_332.api.spec.ts`, `quality/evidence/T7.332.md`. +- **priority**: medium +- **description**: Reproduce the T11 candidate against disposable synthetic fixtures and real C handlers before implementation. Add an independent response/state/side-effect regression, then implement the narrow correction. Coordinate schema and generated changes through the separate reconciliation task; do not weaken the contract to conceal a defect. +- **findings**: F-T11-16 +- **acceptance_criteria**: + - All integer percentages 0..100 survive PUT→GET round trips; specifically 53 and 59 must not become 52 and 58. Preserve representation for existing stored floats with a documented rounding policy. + - Record the before/after behavior and exact failing-to-passing command; use synthetic credentials and disposable storage. +- **validation**: + - `node scripts/quality/run-integration.mjs --task T7.332 --project api --grep @t7_332` + - `npm run typecheck:tests` +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.333: Specify password omission and explicit clear semantics +- **depends_on**: [T7, T11, T13, T7.332, T13.3, T13.4] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_streams_modify.c`, `tests/integration/specs/t7_333.api.spec.ts`, `quality/evidence/T7.333.md`. +- **priority**: medium +- **description**: Reproduce the T11 candidate against disposable synthetic fixtures and real C handlers before implementation. Add an independent response/state/side-effect regression, then implement the narrow correction. Coordinate schema and generated changes through the separate reconciliation task; do not weaken the contract to conceal a defect. +- **findings**: F-T11-17 +- **acceptance_criteria**: + - Omitted onvif_password leaves the secret unchanged; explicit empty clears only as an intentional authorized action. Client form construction never echoes a redacted empty value as a destructive update. + - Record the before/after behavior and exact failing-to-passing command; use synthetic credentials and disposable storage. +- **validation**: + - `node scripts/quality/run-integration.mjs --task T7.333 --project api --grep @t7_333` + - `npm run typecheck:tests` +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.334: Reconcile Streams contracts after behavioral fixes +- **depends_on**: [T7, T12, T7.317, T7.318, T7.319, T7.320, T7.321, T7.322, T7.323, T7.324, T7.325, T7.326, T7.327, T7.328, T7.329, T7.330, T7.331, T7.332, T7.333] +- **agent_type**: local +- **ownership**: `docs/api/contracts/streams.yaml`, `docs/api/contracts/common.yaml`, `docs/api/openapi.yaml`, `tests/contracts/fixtures/streams/auth-verify.auth-disabled.valid.json`, `tests/contracts/fixtures/streams/auth-verify.demo-as-admin.invalid.json`, `tests/contracts/fixtures/streams/auth-verify.demo.valid.json`, `tests/contracts/fixtures/streams/auth-verify.user.valid.json`, `tests/contracts/fixtures/streams/client-config.camelcase-only.invalid.json`, `tests/contracts/fixtures/streams/client-config.valid.json`, `tests/contracts/fixtures/streams/detection-models.403-viewer.valid.json`, `tests/contracts/fixtures/streams/detection-models.no-directory.valid.json`, `tests/contracts/fixtures/streams/detection-models.unsupported-without-reason.invalid.json`, `tests/contracts/fixtures/streams/detection-models.valid.json`, `tests/contracts/fixtures/streams/error.400-retention.valid.json`, `tests/contracts/fixtures/streams/error.400-surface.valid.json`, `tests/contracts/fixtures/streams/error.401.valid.json`, `tests/contracts/fixtures/streams/error.403-put-viewer.valid.json`, `tests/contracts/fixtures/streams/error.404-camera.valid.json`, `tests/contracts/fixtures/streams/error.404-stream.valid.json`, `tests/contracts/fixtures/streams/error.413-publish-url.valid.json`, `tests/contracts/fixtures/streams/error.empty-message.invalid.json`, `tests/contracts/fixtures/streams/error.message-key.invalid.json`, `tests/contracts/fixtures/streams/full.any.generic-schema.valid.json`, `tests/contracts/fixtures/streams/full.configurer.legacy-name-plain-credentials.valid.json`, `tests/contracts/fixtures/streams/full.configurer.missing-onvif-password.invalid.json`, `tests/contracts/fixtures/streams/full.configurer.onvif-url-userinfo.invalid.json`, `tests/contracts/fixtures/streams/full.configurer.onvif.valid.json`, `tests/contracts/fixtures/streams/full.error-message-without-error.invalid.json`, `tests/contracts/fixtures/streams/full.live-only.claims-configure.invalid.json`, `tests/contracts/fixtures/streams/full.live-only.error-status.valid.json`, `tests/contracts/fixtures/streams/full.live-only.leaked-admin-url.invalid.json`, `tests/contracts/fixtures/streams/full.live-only.leaked-detection-url.invalid.json`, `tests/contracts/fixtures/streams/full.live-only.leaked-go2rtc-override.invalid.json`, `tests/contracts/fixtures/streams/full.live-only.leaked-onvif-password.invalid.json`, `tests/contracts/fixtures/streams/full.live-only.leaked-onvif-username.invalid.json`, `tests/contracts/fixtures/streams/full.live-only.leaked-publish-url.invalid.json`, `tests/contracts/fixtures/streams/full.live-only.leaked-sub-stream-url.invalid.json`, `tests/contracts/fixtures/streams/full.live-only.redacted.valid.json`, `tests/contracts/fixtures/streams/full.live-only.sensitive-keys-absent.valid.json`, `tests/contracts/fixtures/streams/full.live-only.url-query-secret.invalid.json`, `tests/contracts/fixtures/streams/full.live-only.url-userinfo.invalid.json`, `tests/contracts/fixtures/streams/full.live-only.url-username-only.invalid.json`, `tests/contracts/fixtures/streams/full.null-tags.invalid.json`, `tests/contracts/fixtures/streams/full.protocol-out-of-enum.invalid.json`, `tests/contracts/fixtures/streams/full.retention-below-inherit.invalid.json`, `tests/contracts/fixtures/streams/full.retention-over-max.invalid.json`, `tests/contracts/fixtures/streams/full.schedule-167-entries.invalid.json`, `tests/contracts/fixtures/streams/full.status-docs-value.invalid.json`, `tests/contracts/fixtures/streams/full.threshold-as-fraction.invalid.json`, `tests/contracts/fixtures/streams/full.unwrapped-object.invalid.json`, `tests/contracts/fixtures/streams/index.json`, `tests/contracts/fixtures/streams/path-encoding.cases.json`, `tests/contracts/fixtures/streams/put-response.200-privacy.valid.json`, `tests/contracts/fixtures/streams/put-response.202-onvif-failed.valid.json`, `tests/contracts/fixtures/streams/put-response.202-onvif-status-without-message.invalid.json`, `tests/contracts/fixtures/streams/put-response.202.valid.json`, `tests/contracts/fixtures/streams/put.clear-with-null.valid.json`, `tests/contracts/fixtures/streams/put.detection-retention-minus-2.invalid.json`, `tests/contracts/fixtures/streams/put.detection-url-file-scheme.invalid.json`, `tests/contracts/fixtures/streams/put.detection-url-too-long.invalid.json`, `tests/contracts/fixtures/streams/put.edit-form.valid.json`, `tests/contracts/fixtures/streams/put.empty-object.valid.json`, `tests/contracts/fixtures/streams/put.eptz-not-string.invalid.json`, `tests/contracts/fixtures/streams/put.eptz-wrong-projection.invalid.json`, `tests/contracts/fixtures/streams/put.is-deleted.invalid.json`, `tests/contracts/fixtures/streams/put.name-in-body.invalid.json`, `tests/contracts/fixtures/streams/put.playback-transport-bogus.invalid.json`, `tests/contracts/fixtures/streams/put.privacy-mixed-with-edit.invalid.json`, `tests/contracts/fixtures/streams/put.protocol-out-of-enum.invalid.json`, `tests/contracts/fixtures/streams/put.publish-and-detection-url.valid.json`, `tests/contracts/fixtures/streams/put.publish-url-http.invalid.json`, `tests/contracts/fixtures/streams/put.publish-url-quote.invalid.json`, `tests/contracts/fixtures/streams/put.record-as-string.invalid.json`, `tests/contracts/fixtures/streams/put.retention-366.invalid.json`, `tests/contracts/fixtures/streams/put.retention-finite-boundary.valid.json`, `tests/contracts/fixtures/streams/put.retention-tristate.valid.json`, `tests/contracts/fixtures/streams/put.schedule-weekday.valid.json`, `tests/contracts/fixtures/streams/put.schedule-wrong-length.invalid.json`, `tests/contracts/fixtures/streams/put.set-privacy-mode.valid.json`, `tests/contracts/fixtures/streams/put.tags-too-long.invalid.json`, `tests/contracts/fixtures/streams/summary.admin.admin-url-without-configure.invalid.json`, `tests/contracts/fixtures/streams/summary.admin.configurer-with-admin-url.valid.json`, `tests/contracts/fixtures/streams/summary.admin.contains-url.invalid.json`, `tests/contracts/fixtures/streams/summary.admin.empty.valid.json`, `tests/contracts/fixtures/streams/summary.admin.missing-can-configure.invalid.json`, `tests/contracts/fixtures/streams/summary.admin.page-beyond-end.valid.json`, `tests/contracts/fixtures/streams/summary.admin.page-size-over-max.invalid.json`, `tests/contracts/fixtures/streams/summary.admin.status-from-detail-enum.invalid.json`, `tests/contracts/fixtures/streams/summary.admin.viewer.valid.json`, `tests/contracts/fixtures/streams/summary.live.contains-admin-url.invalid.json`, `tests/contracts/fixtures/streams/summary.live.contains-onvif-password.invalid.json`, `tests/contracts/fixtures/streams/summary.live.viewer.valid.json`, `tests/contracts/fixtures/streams/summary.lpr.leaks-location.invalid.json`, `tests/contracts/fixtures/streams/summary.lpr.valid.json`, `tests/contracts/fixtures/streams/zones.empty.valid.json`, `tests/contracts/fixtures/streams/zones.valid.json`, `web/js/api/generated/`, `tests/contracts/generated/`, `quality/evidence/T7.334.md`. +- **priority**: high +- **description**: Under the exclusive generation lease, reconcile the exact schemas and affected fixture files after the preceding fixes. The exact fixture list is recorded in ownership; generated directories are exclusively owned outputs of T12. Regenerate only through T12 tooling; no concurrent generation. Preserve separate desired-client and current-wire cases where intentionally different. +- **findings**: policy/integration requirement +- **acceptance_criteria**: + - All valid and invalid fixtures retain explicit reasons and agree with the corrected real backend. + - Generate deterministic artifacts and run real pilot regressions; T18/T19 cannot use stale contracts. +- **validation**: + - `node scripts/quality/contracts.mjs --generate` + - `node scripts/quality/contracts.mjs --check` + - `node --test tests/quality/contracts.test.mjs` + - `npm --prefix web run typecheck` +- **status**: Not Completed +- **log**: +- **files edited/created**: + +### T7.335: Resolve proposed ownership and exception review records +- **depends_on**: [T7, T7.1] +- **agent_type**: local +- **ownership**: `quality/reviews/exception-ownership.json`, `quality/evidence/T7.335.md`. +- **priority**: high +- **description**: Collect accountable owners and explicit approvals/denials for T1/T6 proposals and any new residual exceptions. Existing maintainers/ strings and proposed dates are not acceptance. Prepare a concrete review record; leave pending entries unresolved until actual authorized reviewer input arrives. +- **findings**: policy/integration requirement +- **acceptance_criteria**: + - Every unresolved proposal names its exact finding/gap, requested owner, exposure, rationale, proposed expiry and approval state. + - No approval, repository protection change, CODEOWNERS assignment or publication is inferred from implementation authorization. +- **validation**: + - `python3 -m json.tool quality/reviews/exception-ownership.json` +- **status**: Blocked — review prepared, awaiting compatible fix or maintainer decision +- **log**: Concrete review packet committed in `713b3025`; no package mutation, approval or disposition change. Current development-tool advisory ranges and dependency parents checked; production web audit zero. See task evidence and `quality/reviews/exception-ownership.json`. +- **files edited/created**: + +## Expanded test typing tasks (T9) + +### T9.1: Repair load-test and page-helper type boundaries +- **depends_on**: [T9] +- **agent_type**: worker +- **ownership**: `tests/load/config.ts`, `tests/load/reporter.ts`, `tests/load/runner.ts`, `tests/integration/pages/StreamsPage.ts`, `tests/integration/pages/TimelinePage.ts`, `quality/evidence/T9.1.md`. +- **description**: Repair three array-index return types, two unknown caught errors, and date extraction. Use checked existence/narrowing, preserve behavior for valid inputs, and reject impossible empty selections explicitly. No load execution or test removal. +- **acceptance_criteria**: All six initial diagnostics in these five files disappear without broad declarations, ts-ignore, or new `any`; valid CLI/scenario/report/page behavior stays intact. +- **validation**: `npm run typecheck:tests` (initial 38 diagnostics; this cluster must reach zero while unrelated clusters remain RED); Node assertions for parseArgs valid/missing values and zero-request runLoadTest without network; final whole-config GREEN owned by T9.4. +- **status**: Completed (commit `d36e4e65`) +- **log**: Six cluster diagnostics repaired with checked values and narrowed errors; no load traffic. + + +### T9.2: Type media DOM and mocked routes +- **depends_on**: [T9.1] +- **agent_type**: worker +- **ownership**: `tests/integration/specs/investigation.ui.spec.ts`, `tests/integration/specs/recordings.ui.spec.ts`, `tests/integration/specs/streams.ui.spec.ts`, `tests/integration/specs/timeline.ui.spec.ts`, `quality/evidence/T9.2.md`. +- **description**: Narrow selected video elements to the HTMLVideoElement contract; retain controls/readyState/play/pause assertions. Type mocked route setup parameters as Playwright Page so handlers infer Route. Use fixed ISO date slicing for non-optional date strings. Handle controlsList as a narrow browser capability (DOMTokenList) because it is not part of TypeScript's portable HTMLMediaElement interface. +- **acceptance_criteria**: All 23 original diagnostics across these four files disappear; all runtime test definitions/assertions remain present; no ambient browser-wide `any` added. +- **validation**: `npm run typecheck:tests` with zero cluster errors; compare Playwright --list test identities to baseline (ignore moved line numbers); final full GREEN in T9.4. +- **status**: Completed (commit `90f26a8d`) +- **log**: 23 media DOM/route diagnostics repaired across four files without changing test identities. + + +### T9.3: Preserve timeline/snapshot fixture bounds +- **depends_on**: [T9.2] +- **agent_type**: worker +- **ownership**: `tests/integration/specs/timeline-boundary.ui.spec.ts`, `tests/integration/specs/stream-snapshot-requests.ui.spec.ts`, `quality/evidence/T9.3.md`. +- **description**: Validate parsed local date/time components and nonempty segment fixtures; narrow fixed fixture entries and captured URLs through explicit checks or known tuple definitions. Preserve existing route responses and snapshot/timeline assertions, rejecting malformed fixture setup instead of changing tests to pass. +- **acceptance_criteria**: All eight original diagnostics in these two files disappear without optional-default values hiding missing test data; test identities remain unchanged. +- **validation**: `npm run typecheck:tests` with zero cluster errors; Playwright --list identities unchanged; final full GREEN in T9.4. +- **status**: Completed (commit `688eb332`) +- **log**: Eight timeline/snapshot diagnostics repaired with explicit fixture bounds and tuple types. + + +### T9.4: Integrate the legacy JS test boundary and verify coverage +- **depends_on**: [T9.3] +- **agent_type**: local +- **ownership**: `tests/tsconfig.json`, `tests/integration/recordings-url-utils.ui.spec.ts`, optional narrowly scoped test interop helper under `tests/integration/fixtures/`, `quality/evidence/T9.4.md`, `quality/evidence/T9.md`. +- **description**: Resolve the existing recordings JS import without wildcard-any modules or hiding test files. Keep the exact legacy JS boundary explicit and runtime-checked where values cross into the test. Any incremental allowJs/checkJs setting must inventory the exact imported application files as still unchecked and assign retirement to T34; it is not migration completion. Preserve the four existing URL utility test definitions and assertions. Reconcile compiler coverage and discovery across all clusters. +- **acceptance_criteria**: Root typecheck is GREEN for all existing integration/load TS and configuration; no unchecked test file/excluded diagnostic; 213 Playwright tests in 28 files remain discovered; no load traffic sent. Legacy application JS remains explicitly accounted for in evidence. +- **validation**: `npm run typecheck:tests`; `npx playwright test --list --reporter=list`; inspect `tsc --listFilesOnly -p tests/tsconfig.json` against `rg --files tests/integration tests/load`; run the four URL utility tests with a temporary isolated Playwright config disabling global setup/teardown (they need neither browser launch nor server); all results captured under `build/eng-artifacts/T9/`. +- **status**: Completed (commit `b0bcf4c2`) + +- **log**: Root strict typecheck GREEN; all 48 TS files included, 213 Playwright identities preserved, four existing URL utility tests pass without server/browser/load. Three legacy JS dependencies explicitly inventoried as unchecked. + +Coordinator verified this serial task chain and inserted T9.4 into T13/T15 before child edits. + +## Expanded C boundary tasks (T31) + + +### T31.1: Reproduce stream-state mutex disposal imbalance +- **depends_on**: [T31, T13] +- **agent_type**: worker +- **ownership**: `tests/unit/test_stream_state_disposal.c`, `tests/unit/cmake/eng/stream_state_disposal.cmake`, `quality/evidence/T31.1.md`. +- **description**: Wrap pthread_mutex_destroy in a focused native test and track only the two mutex addresses of created states. Test normal removal, extra-ref refusal, global shutdown, repeated shutdown and repeated lifecycle cycles. Also reproduce registry lookup from a wrapped stop callback in a child process with a bounded deadline; verify teardown rejects create/remove/recursive shutdown during the unlocked callback. Add registration through T13's fragment seam. Do not modify production code in this task. +- **acceptance_criteria**: The original shutdown path fails because state_mutex is not destroyed and an active-state stop callback deadlocks when looking up the registry; normal removal and refusal assertions establish unchanged preconditions. No live stream or remote server is used. +- **validation**: `python3 scripts/quality/run-native.py --task T31.1 --regex '^test_stream_state_disposal$'` must fail for the expected missing destruction, with exact RED output retained. +- **status**: Completed (commits `172d5fb6`, `a3074ea3`) +- **log**: Five focused regressions reproduced missing mutex destruction and bounded callback-reentry deadlock before the fix; normal removal/refusal controls retained. Ignored CMake fragment explicitly tracked. +- **files edited/created**: `tests/unit/test_stream_state_disposal.c`, `tests/unit/cmake/eng/stream_state_disposal.cmake`, `quality/evidence/T31.1.md` + +### T31.2: Unify final disposal of stream-state resources +- **depends_on**: [T31.1] +- **agent_type**: worker +- **ownership**: `src/video/stream_state.c`, `quality/evidence/T31.2.md`. +- **description**: Introduce a private final-disposal helper destroying both initialized per-state mutexes before freeing. Global shutdown must release the registry mutex around existing stop callbacks, with a lock-protected teardown guard rejecting registry create/remove/recursive shutdown while ownership is handed off. Reacquire the registry lock before final disposal. Document quiescence/lock preconditions; keep public API, stop ordering, reference checks and persisted state unchanged. +- **acceptance_criteria**: T31.1 regressions pass; every final-disposal caller uses the helper and no duplicate destroy is added. No general concurrency redesign or new allocation is introduced; stop callbacks can safely reenter registry lookup during teardown. +- **validation**: `python3 scripts/quality/run-native.py --task T31.2 --regex '^test_stream_(state_disposal|state|manager)$'`. +- **status**: Completed (commit `9a7e6188`) +- **log**: Shared final disposal destroys both mutexes; guarded shutdown releases the registry lock around stop callbacks. Five new regressions and 34 related Unity tests pass under ASan/UBSan. +- **files edited/created**: `src/video/stream_state.c`, `quality/evidence/T31.2.md` + +### T31.3: Verify resource-disposal regression under sanitizers +- **depends_on**: [T31.2] +- **agent_type**: local +- **ownership**: `quality/evidence/T31.3.md`. +- **description**: Rerun the focused disposal/state/manager tests in the isolated native runner and its ASan/UBSan profile; record exact toolchain, revision and flags. Repeat the T13 go2rtc API smoke on the rebuilt integration profile and require graceful server/child cleanup within the existing deadline. Assert repeated-cycle success and no diagnostics. Physical device budgets remain deferred and are not replaced by sanitizer results. +- **acceptance_criteria**: Functional and ASan/UBSan runs pass with nonzero test discovery, no invalid/double destroy observations, and no sanitizer errors; the reproduced go2rtc smoke exits gracefully without fallback kill. No runtime performance claim is made. +- **validation**: `python3 scripts/quality/run-native.py --task T31.3 --regex '^test_stream_(state_disposal|state|manager)$'`; run the same selection with the sanitizer option produced/documented by T13. +- **status**: Completed (commit `00f9394e`) +- **log**: Committed production revision passes 34 tests in both Release and ASan/UBSan. Exact four-case real go2rtc reproduction passes and exits gracefully in 3647 ms without forced parent/child cleanup; physical calibration remains deferred. +- **files edited/created**: `quality/evidence/T31.3.md` + +## Expanded lint compatibility tasks (T10) + +### T10.1: Supply attribute translation keys and regression fixtures +- **depends_on**: [T10] +- **agent_type**: worker +- **ownership**: `web/public/locales/en.json`, `tests/quality/localized-attributes.test.mjs`, `quality/evidence/T10.1.md`. +- **description**: Add explicit English keys for newly exposed placeholder examples, plan labels/shortcuts, recording thumbnail/count and tag-removal labels. Reuse existing settings.tabsLabel, recordings.download/protect/unprotect keys. Test real i18n interpolation and English fallback for a partial locale; do not translate protocol examples or add fabricated translations to other catalogs. +- **acceptance_criteria**: + - Required source keys exist and interpolate variable values, singular/plural counts, shortcut labels and multiline examples without raw key names or dropped parameters. + - Existing English output is preserved except the intentional reuse of the more descriptive existing Protect/Unprotect recording labels. + - No i18n literal allowance is increased; non-English partial-catalog fallback follows existing behavior. +- **validation**: + - `node --test tests/quality/localized-attributes.test.mjs` + - `npm --prefix web test -- --runInBand --testPathPatterns='i18n|locale-coverage'` +- **status**: Completed (commit `a69d3bad`, `6591c330`) +- **log**: Three real interpolation/fallback tests pass after RED; 14 English keys with existing partial-locale fallback, preserving translated Undo/Redo action names. +- **files edited/created**: + +### T10.2: Localize settings and stream editor attributes +- **depends_on**: [T10.1] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/SettingsView.jsx`, `web/js/components/preact/StreamConfigModal.jsx`, `web/js/components/preact/settings/DetectionTab.jsx`, `web/js/components/preact/settings/Go2rtcTab.jsx`, `web/js/components/preact/settings/StorageTab.jsx`, `quality/evidence/T10.2.md`. +- **description**: Replace the nine newly exposed literals in five settings files with T10.1 keys. Remove redundant English fallback for the existing settings.tabsLabel key. Preserve dynamic bucket interpolation and full multiline URL/config examples, handlers, field values and disabled-state logic. +- **acceptance_criteria**: + - All reported attribute literals in these five files pass the strengthened i18n rule with unchanged baseline counts. + - Example URL schemes, credentials-as-examples, protocol options, whitespace/newlines and storage bucket interpolation remain accurate. + - No strings are moved to helper constants merely to evade AST inspection. +- **validation**: + - `node --test tests/quality/localized-attributes.test.mjs` + - `(cd web && node_modules/.bin/eslint js/components/preact/SettingsView.jsx js/components/preact/StreamConfigModal.jsx js/components/preact/settings/DetectionTab.jsx js/components/preact/settings/Go2rtcTab.jsx js/components/preact/settings/StorageTab.jsx)` + - `npm --prefix web test -- --runInBand --testPathPatterns='i18n|locale-coverage|go2rtcSettings'` +- **status**: Completed (commit `f2b93b68`) +- **log**: Five settings/editor files localized; scoped lint zero errors, 41 relevant tests pass. +- **files edited/created**: + +### T10.3: Localize live and recording attributes and close lint integration +- **depends_on**: [T10.2] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/live/LiveBuildingPlan.jsx`, `web/js/components/preact/live/PlanSketchControls.jsx`, `web/js/components/preact/recordings/RecordingsGrid.jsx`, `web/js/components/preact/recordings/RecordingsTable.jsx`, `web/js/components/preact/recordings/TagsOverlay.jsx`, `quality/evidence/T10.3.md`. +- **description**: Replace remaining newly detected attribute literals with T10.1 keys and existing download/protection keys. Obtain i18n in TagChip at component top level, preserve callback and focus behavior, and preserve count/singular/plural and filename/size interpolation. Run final whole-tree lint and compatibility tests after serial source fixes. +- **acceptance_criteria**: + - No new i18n errors remain; the 897 existing accessibility warnings stay explicit rather than being suppressed or rebaselined. + - Labels retain the stream/plan/tag/count/size values and shortcut information; existing action handlers remain unchanged. + - T10 compatibility tests and relevant localization/live/recording regressions pass; baseline JSON bytes remain unchanged. +- **validation**: + - `node --test tests/quality/lint-compat.test.mjs tests/quality/localized-attributes.test.mjs` + - `npm --prefix web run lint` + - `npm --prefix web test -- --runInBand --testPathPatterns='i18n|locale-coverage|planSketch|buildingPlan|recording|tag'` +- **status**: Completed (commit `4f82c1e2`) +- **log**: Five live/recording files localized; 31 focused tests plus all 571 frontend tests pass, lint zero errors/897 unchanged warnings, baseline byte-identical. +- **files edited/created**: + +## Expanded scanner integration tasks (T14) + +### T14.1: Capture scanner provenance and enforce explicit lane scope +- **depends_on**: [T6, T8, T7.1] +- **agent_type**: local +- **ownership**: `scripts/quality/scanner_run.py`, `scripts/quality/findings_lane.py`, manifest validation only in `scripts/quality/findings.py`, `tests/quality/test_scanner_ci.py`, manifest negative cases in `tests/quality/test_findings.py`, `quality/evidence/T14.1.md`. +- **description**: Capture real subprocess exit/status, tool version, nonempty actual scanned-file evidence, report hashes and revision in a run manifest. Add explicitly scoped per-lane evaluation announcing all omitted scanners; it cannot substitute for the full expected scanner coverage gate. Harden required manifest validation without changing approval policy, baseline-writing authorization, or scanner normalization. T14 switches the required-manifest policy when integrated. +- **acceptance_criteria**: + - Missing, malformed, stale, wrong-revision or tampered required provenance fails closed; crashed commands cannot become clean reports. + - Clean/new-finding/scanner-error fixtures exercise the actual subprocess and checker chain. Normal checks leave baseline untouched. + - Candidate source inventory is labeled as such and cannot masquerade as actual CodeQL scan evidence; absent evidence remains missing. + - Lane results are explicitly partial with omitted expected scanner identities; the global check still requires all expected scanners. +- **validation**: + - `python3 -m unittest discover -s tests/quality -p test_scanner_ci.py` + - `python3 -m unittest discover -s tests/quality -p test_findings.py` + - Exercise clean/new/error lane commands against isolated scanner fixtures and verify missing-global-scanner failure. +- **status**: Completed (commit `7a304140`) +- **log**: 22 subprocess/provenance/partial-lane tests and 69 findings tests pass. Required manifests fail closed for missing, stale, tampered and wrong-revision evidence; lanes explicitly list omitted scanners and never claim global coverage. +- **files edited/created**: `scripts/quality/scanner_run.py`, `scripts/quality/findings_lane.py`, `scripts/quality/findings.py`, `tests/quality/test_scanner_ci.py`, `tests/quality/test_findings.py`, `quality/evidence/T14.1.md` + +## Expanded API baseline corrections (T13) + +### T13.1: Verify policy-user self-update denials without side effects +- **depends_on**: [T13] +- **agent_type**: worker +- **ownership**: `tests/integration/specs/auth.api.spec.ts`, `quality/evidence/T13.1.md`. +- **description**: Correct the existing policy-user role-change expectation to the actual exact 400 rejection (policy metadata validation precedes self-permission checks). Independently assert exact 403 for prohibited self activation/password/CIDR changes. Snapshot relevant user/grant state before/after, preserve working original credentials, and verify no admin access is gained. No production authorization change. +- **acceptance_criteria**: Tests discriminate invalid role metadata from forbidden mutations, assert unchanged persisted user/grants, and preserve existing coverage; no broad success/error status list is used. +- **validation**: `npm run typecheck:tests`; `node scripts/quality/run-integration.mjs --task T13.1 --project api --grep 'Users API'`; original RED is retained in T13 full API evidence. +- **status**: Completed (commit `8f11a3a0`) +- **log**: Nine Users API tests pass with exact denial statuses and persisted/security invariants; original credentials remain valid and rejected changes confer no admin access. Root types pass. +- **files edited/created**: + +### T13.2: Verify accepted stream disable and permanent deletion complete +- **depends_on**: [T13.1] +- **agent_type**: worker +- **ownership**: `tests/integration/specs/comprehensive.api.spec.ts`, `quality/evidence/T13.2.md`. +- **description**: Use self-contained enabled fixtures, assert exact 202 accepted responses and permanent:false for default soft deletion, then boundedly poll the DB-backed list for disabled state. Exercise permanent=true separately, require accepted response and poll disappearance/detail404. Preserve mutation assertions and cleanup; never treat a pre-disabled/missing fixture as successful async completion. +- **acceptance_criteria**: Both soft-disable and permanent-delete behavior are verified through completion with bounded polling; the old broad 200/204/404 list is replaced by the observed contract, not expanded. Full API suite passes or additional actual failures become explicit bounded tasks. +- **validation**: `npm run typecheck:tests`; `node scripts/quality/run-integration.mjs --task T13.2 --project api --grep @api`. +- **status**: Completed (commit `486a3a84`) +- **log**: Full API suite passes 48/48 with no skips. Enabled soft-delete fixture reaches disabled DB-backed state; permanent delete reaches list absence and detail 404. Real go2rtc teardown is graceful without forced cleanup. +- **files edited/created**: + +### T13.3: Reject stream deletion when its worker cannot start +- **depends_on**: [T13.2] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_streams_modify.c`, `tests/unit/test_delete_stream_launch_failure.c`, `tests/unit/cmake/eng/delete_stream_launch_failure.cmake`, `quality/evidence/T13.3.md`. +- **description**: Reproduce the source-inspected delete handler bug with a bounded pthread_create failure injection: it currently prepares a 202 accepted response then only logs worker creation failure. Return an explicit server error instead of false acceptance when launch fails, preserving successful asynchronous deletion and authorization ordering. Response fields are not transmitted until the handler returns; do not assume the misleading old comment is accurate. +- **acceptance_criteria**: RED test shows 202 on failed launch; GREEN rejects it with 5xx and unchanged persisted/operational stream, no leaked task or callback execution. Successful launch remains 202 and T13.2 completion tests pass. No deletion/auth redesign. +- **validation**: `python3 scripts/quality/run-native.py --task T13.3 --regex '^test_delete_stream_launch_failure$'`; rerun focused T13.2 deletion API tests against the rebuilt server. +- **status**: Completed (commits `cb8f0063`, `853c0d9c`, `a737c4f0`) +- **log**: Injected worker failure reproduced false202 before production fix. Final sanitizer3/3 and rebuilt deletion API2/2 pass; graceful teardown4450ms without forced cleanup. +- **files edited/created**: + +### T13.4: Reject stream updates when their worker cannot start +- **depends_on**: [T13.3] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_streams_modify.c`, `tests/integration/specs/comprehensive.api.spec.ts` existing PUT case only, `tests/unit/test_put_stream_launch_failure.c`, `tests/unit/cmake/eng/put_stream_launch_failure.cmake`, `quality/evidence/T13.4.md`. +- **description**: Reproduce the analogous PUT worker-creation failure discovered during T13.3: the handler prepares a 202 response and only logs pthread_create failure. Return a server error after disposing the task. Keep successful asynchronous updates and authorization behavior unchanged; source inspection must distinguish any existing synchronous side effects before claiming state invariance. +- **acceptance_criteria**: Tests first demonstrate false acceptance under injected launch failure; after fix return 5xx, free owned task resources once and do not execute the worker. Use a simple non-ONVIF update to assert persisted and runtime stream fields unchanged on rejection. Strengthen the existing log-only PUT API case to require initial enabled state, exact 202, bounded persisted completion and preserved unrelated fields. Authorization controls retain coverage. Report any prelaunch side effects as separate bounded findings rather than extending this task silently. +- **validation**: `python3 scripts/quality/run-native.py --task T13.4 --regex '^test_(put|delete)_stream_launch_failure$'`; run focused real API update/deletion checks against a rebuilt server. +- **status**: Completed (commits `43c65a16`, `2da05c1e`, `dc292a0c`) +- **log**: Injected PUT launch failure reproduced false202, now returns500 with owned task disposal. DELETE+PUT sanitizer5/5 and rebuilt Streams API14/14 pass, graceful4246ms. Existing prelaunch side effects remain separately mapped F-T11-11/-12/-15. +- **files edited/created**: + +## Independent foundation command integration (T15) + +### T15.1: Route existing local and CI integration commands through owned runners +- **depends_on**: [T13, T9.4, T10.3, T12] +- **agent_type**: local +- **ownership**: root `package.json` integration scripts only, `scripts/quality/run-integration.mjs` CLI/config/artifact compatibility, `tests/quality/test_runners.py`, `.github/workflows/integration-test.yml` existing Playwright invocations/artifacts/cleanup, `docs/TESTING.md` or an exact existing contributor test guide, `quality/evidence/T15.1.md`. +- **description**: Keep existing npm test/api/ui/go2rtc and workflow health/UI shard commands functional after T13 requires owned run context. Support explicit all-project selection, safe file/shard/grep filters, and discovery without starting a server. Preserve the known health/MQTT lane's feature intent via a bounded named configuration; never adopt arbitrary external production configuration. Replace old raw Playwright setup calls with the wrapper and the prebuilt binary. Retain per-run reports/logs and remove broad pkill/tmp deletion now handled by ownership-aware cleanup. This is command integration, not risk acceptance or full T15 qualification. +- **acceptance_criteria**: + - Existing health/UI shard CI commands create isolated owner/config/PID context; root npm commands cannot bypass runner checks. + - Safe CLI forwarding preserves requested test selection; zero-selected tests and discovery errors fail. Unknown dangerous configuration overrides are rejected. + - Health/MQTT coverage is not silently disabled, UI shards stay distinct, and meaningful artifacts remain available after failures. + - No exception approval, baseline update, dependency downgrade, remote push, or repository setting change occurs. +- **validation**: + - `python3 -m unittest discover -s tests/quality -p test_runners.py` + - `npm run typecheck:tests` + - Run safe discovery for npm API/UI and both workflow shard selections; compare identities to pre-change discovery without starting load or servers. + - Parse workflow YAML and inspect exact existing job changes; real remote CI remains pending until explicitly run. +- **status**: Completed (commit `3c15c0d7`) +- **log**: 22 runner tests and root TS pass; all 213 discovery identities preserved, including health files and UI shards. Existing npm/CI routes use owned runs with retained logs; no native runtime claim or exception approval. +- **files edited/created**: + +### T15.2: Register independent foundation statuses and proposed enforcement settings +- **depends_on**: [T4, T9.4, T10.3, T12, T14.1, T15.1] +- **agent_type**: local +- **ownership**: `.github/workflows/engineering-foundation.yml`, `quality/required-checks.json`, `tests/quality/foundation-workflow.test.mjs`, `quality/evidence/T15.2.md`. +- **description**: Register separate type/lint/unit/schema/asset and quality-tooling CI statuses using existing commands. Run both asset profiles in isolated jobs and check without updating budgets or generated artifacts. Prepare concrete required-check identities and path behavior for T38; leave the future real-backend contract lane explicitly pending T39. No remote rules mutation and no dependency exception approval. +- **acceptance_criteria**: PR jobs have no path filters or conditional success bypass; failures propagate and diagnostics upload on failure. Only existing nonempty suites run. Proposed settings do not claim actual enforcement, full scanner coverage or migration completion; T15 acceptance remains gated by its existing dependencies. +- **validation**: Tests first reject missing workflow/identity mapping, omitted build profile, mutating check commands and skipped required jobs; execute registered local type/lint/unit/schema/tooling checks and both build/asset commands. No remote publication required. +- **status**: Completed (commit `caf23285`) +- **log**: Seven foundation jobs and concrete un-applied status mapping registered. Tests-first controls pass;63 Node+116 Python tooling tests,571 web unit tests, types/schema/lint and both asset profiles pass. T15 acceptance and remote enforcement remain open. +- **files edited/created**: + +### T15.3: Register verified native regressions in the existing PR lane +- **depends_on**: [T3, T13.4, T15.1, T28, T31.3] +- **agent_type**: local +- **ownership**: `.github/workflows/integration-test.yml` Unity and go2rtc build/CTest selections only, `tests/quality/native-ci.test.mjs`, `.github/workflows/engineering-foundation.yml` (tooling commands only), `tests/quality/foundation-workflow.test.mjs`, `quality/evidence/T15.3.md`. +- **description**: Include existing writer-watchdog, segment-timestamp and faststart tests plus newly verified stream-state disposal, DELETE/PUT launch failure, storage mux/backpressure and deletion-ledger regressions in both the existing Unity build list and its CTest selection. The broad existing stream_state regex already selects the new disposal test; explicitly build it rather than leaving a not-built failure. Preserve the previous selection and require nonempty discovery. Follow-up includes verified stale-prune native controls and real historical migration guard/generator tests in the existing Foundation tooling job, with read-only generated-header drift check. +- **acceptance_criteria**: All thirty-one added targets are built and selected, all previous entries retained, no empty success or failure masking. Workflow validation passes; local producer evidence covers actual test execution. Hosted execution remains unverified until authorized publication. +- **validation**: Tests first inspect actual YAML build/CTest commands for matching inclusion; actionlint and recorded focused native producer runs. No source/fixture change or remote action. +- **status**: Completed locally (commit `12dffcda`) +- **log**: Thirty-one verified native targets added across Unity/go2rtc build and CTest selections; previous targets retained and empty discovery rejected. Each registration had expected missing-target RED before wiring. Latest41 workflow checks and actionlint pass. Foundation also runs real migration/generator checks, generated-header drift checks and34 soak unit checks. Earlier broad quality run89Node+158Python passed; exact artifacts and producer runs are in T15.3.md. Hosted execution and scanner dispositions remain unverified/unapproved. +- **files edited/created**: + +## Expanded media recovery tasks (T27) + +### T27.0: Produce bounded native and actual-source media recovery checks +- **depends_on**: [T3, T13, T28.2] +- **agent_type**: worker +- **ownership**: T27 assigned native tests, `tests/fixtures/eng/media/`, `tests/operational/media/`, `tests/unit/cmake/eng/media.cmake`, `quality/evidence/T27.md`. +- **description**: Materialize isolated recovery/segment evidence with declared one-keyframe overlap tolerance, real source disconnect/restore and H264/AAC storage-pressure recovery. Preserve genuine RED outcomes as prerequisites for bounded fixes, no expected-failure suppression. +- **acceptance_criteria**: Native/oracle controls execute; real scenarios record frame/media/DB/metadata/recovery and graceful cleanup results, with source/config provenance and bounded deadlines. Failing product observations have exact evidence and successors. +- **validation**: T27 native selection and `tests/operational/media/recovery.py` documented scenario commands. +- **status**: Completed — tests-only producer, real collision failure retained (commit `4c513e4f`) +- **log**: 29 native and7 oracle tests pass; real video-only recovery passes. H264/AAC pressure/source restart reproduces two completed rows referencing one overwritten path (72731-byte first row vs94013-byte file). No production fix claimed; T27.1 owns closure. +- **files edited/created**: Exact8 paths in commit4c513e4f; evidence retains all artifacts. + +### T27.1: Prevent MP4 path reuse from overwriting completed recordings +- **depends_on**: [T27.0, T28.2] +- **agent_type**: worker +- **ownership**: `src/video/recording_path.c`, contract comments only as needed in `include/video/recording_path.h`, bounded rotation/retry integration in `src/video/mp4_writer_thread.c`, `tests/operational/media/test_mp4_path_collision.c`, its `tests/unit/cmake/eng/media.cmake` registration, narrow reservation wrapper updates in `tests/unit/test_mp4_forced_reconnect.c` and `tests/operational/storage/test_storage_writer_backpressure.c`, `quality/evidence/T27.1.md`. +- **description**: Reproduce same-second path reuse with owned existing media, then ensure new segment creation cannot truncate a previous recording on rapid rotation/retry/restart. Inspect filename consumers before choosing a bounded uniqueness/reservation strategy; preserve timestamp meaning, supported layouts, mp4 extension, placement.object_key and DB path agreement. Keep planned-path helpers pure; atomically reserve immediately before the actual segment call, retain ownership through the call and remove only the same-inode still-empty reservation on failure. Handle buffer/allocation/reservation failure explicitly and clean up any owned reservation on failure; do not claim atomic exclusion from a racy existence check alone. +- **acceptance_criteria**: Tests first reproduce collision; GREEN preserves previous bytes/metadata and gives subsequent recordings distinct identities, including rapid retry/restart and existing-file controls. No existing recording is truncated, placement paths remain consistent, and the exact real H264/AAC pressure/source-restart scenario passes distinct-file/path/size/placement assertions. The separately reproduced buffered timestamp failure remains gated by T27.2. No storage-schema redesign or weakening of graceful teardown/oracles. +- **validation**: Focused collision sanitizer target plus existing path/layout/storage-policy, forced-reconnect/audio/boundary and recording-sync/playback controls selected after source review; rerun exact real failing T27 scenario and verify both source modes before original T27 closes. +- **status**: Completed (commit `3b62c681`) +- **log**: Focused collision4RED→12GREEN; zero/negative-duration2RED→GREEN. Thirteen sanitizer targets/111Unity pass. Real pressure case retains separate original72731-byte andnew94013-byte files with matching object keys/DB sizes; timing defect preserved forT27.2. +- **files edited/created**: + +### T27.2: Reset stale media state when storage pressure pauses recording +- **depends_on**: [T27.1] +- **agent_type**: worker +- **ownership**: `src/video/mp4_writer_thread.c` emergency-pause branch, `tests/operational/media/test_mp4_pressure_reconnect.c`, its `tests/unit/cmake/eng/media.cmake` registration, `quality/evidence/T27.2.md`. +- **description**: Reproduce retained pre-pause input/pending-keyframe/audio state being flushed after source restart with current wall-clock metadata. Reset/dispose the old reader, pending boundary packet and audio state on entering emergency storage pause, before sleeping, so recovery opens a fresh source. Preserve pressure policy, shutdown deadlines, one-frame normal boundary tolerance and ordinary reconnect semantics. +- **acceptance_criteria**: Tests first demonstrate stale state survives pause; GREEN disposes/resets it once without resource errors. Actual statvfs-based H264/AAC pressure/source-restart scenario passes unchanged duration/DB timestamp/size/decoded-media oracle, as does video-only recovery. No old packet is carried across the paused-session boundary and normal graceful cleanup remains required. +- **validation**: Focused pressure-reconnect sanitizer target plus existing watchdog/forced-reconnect/audio/boundary/pressure controls; rebuild actual server and rerun exact two T27 scenarios without relaxing their limits. +- **status**: Completed (commit `586d8d89`) +- **log**: Four stale-state controls failed first, then84/84 native cases pass. Both unchanged real scenarios pass:14.700s video-only and12.485s H264/AAC+pressure,12files decode and matchDBsizes/timing/continuity; all owned processes exit normally. +- **files edited/created**: + +## Expanded storage fault tasks (T28) + +### T28.0: Implement isolated storage fault regressions +- **depends_on**: [T3, T13] +- **agent_type**: worker +- **ownership**: T28 test/fixture/registration paths, `quality/evidence/T28.md`. +- **description**: Produce T28 tests-only coverage with real SQLite, owned files, scoped faults and playable recovered media. Record each genuine RED production defect without marking it expected or weakening assertions. T28.1/T28.2 own fixes; T28 final integration requires their GREEN results. +- **acceptance_criteria**: Each new assertion runs under the isolated native runner; existing controls pass; reproduced product failures have exact evidence and bounded successors. No production changes or host storage disruption. +- **validation**: T28 focused sanitizer commands and fault-injection diagnostics in task evidence. +- **status**: Completed — tests-only producer, genuine failures retained (commit `0eb7e5aa`) +- **log**: 70 Unity cases execute under ASan/UBSan:68 pass,2 genuine RED product defects (unavailable legacy root metadata loss, swallowed EROFS segment failure). Scoped fixtures avoid host disruption. T28.1/T28.2 own fixes; originalT28 acceptance pending. +- **files edited/created**: + +### T28.1: Preserve legacy recording metadata while its root is unavailable +- **depends_on**: [T28.0] +- **agent_type**: worker +- **ownership**: `src/storage/storage_deletion.c`, narrowly related missing-file controls in `tests/unit/test_storage_manager_retention.c`, `tests/unit/test_storage_retention_sqlite.c` owned-root setup for real deletion fixture only, `quality/evidence/T28.1.md`. +- **description**: Fix the T28 unavailable-root regression: legacy deletion interprets ENOENT beneath an unavailable recordings root as authoritative file absence and finalizes metadata deletion. Keep deletion pending/retryable while root availability cannot be established; after restoration reclaim eligible expired files normally. Preserve registered-target mount guards, protected media and legitimate missing-file cleanup. Document the exact legacy-root identity rule, avoiding a new persistence or storage redesign. +- **acceptance_criteria**: Original unavailable-root regression reaches GREEN with metadata/file retained until restoration; ordinary existing-file and legitimately absent-file deletions still complete. Protected/open files and fault retry controls remain intact. No pretend successful deletion while root is unavailable. +- **validation**: `python3 scripts/quality/run-native.py --task T28.1 --regex '^test_storage_(manager_retention|retention_sqlite|deletion.*)$'`; exact owned-root rename/restore regression under ASan/UBSan. +- **status**: Completed (commit `6d254658`) +- **log**: Tests-first legacy root guard preserves metadata on unavailable/unknown roots.39/39 sanitizer cases pass, including healthy-root absent-file cleanup and owned-root fixture correction. No storage-schema change; configured-directory check is not persisted mount-identity proof. +- **files edited/created**: + +### T28.2: Stop MP4 segments on terminal read-only write failures +- **depends_on**: [T28.0] +- **agent_type**: worker +- **ownership**: `src/video/mp4_segment_recorder.c`, narrowly related controls in `tests/operational/storage/test_storage_recording_faults.c`, `quality/evidence/T28.2.md`. +- **description**: Fix the T28 EROFS regression: an actual segment packet mux failure repeats and a later successful trailer overwrites it with success. Treat read-only storage write failure as terminal alongside disk-full/I/O failure across the existing packet paths, preserve its negative result through cleanup and keep timestamp recovery behavior distinct. Use the existing recording I/O diagnostics and close owned resources without new dependencies. +- **acceptance_criteria**: Injected EROFS stops on the first affected packet with negative result, existing ENOSPC/EIO controls pass, no sanitizer issue, and removing the fault produces decodable media. Existing timestamp/audio watchdog behavior remains unchanged. +- **validation**: Run T28 real segment fault target and existing segment/timestamp/audio recovery selections under the isolated sanitizer runner; record exact selections and decoding assertions. +- **status**: Completed (commit `e56fc168`) +- **log**: Terminal EROFS handling added to existing final-video/video/audio paths; first failed packet retains negative status and cleanup. Five real mux fault controls pass, including final keyframe, plus existing timestamp/audio/watchdog controls. T28 full integration73/73 passes. T27 initially over-strict zero-overlap oracle is being corrected to its declared tolerance; no boundary behavior changed. +- **files edited/created**: + +## Expanded shutdown observation tasks (T29) + +### T29.0: Reproduce false successful coordinator timeout +- **depends_on**: [T3, T13] +- **agent_type**: worker +- **ownership**: `tests/unit/test_shutdown_coordinator.c`, `quality/evidence/T29.0.md`. +- **description**: Strengthen the existing timeout test, which currently discards its result, to require false on deadline and preserve actual live component state. Exercise actual successful completion, condition signaling without completion, and reuse of genuinely stopped component slots. Source remains unchanged until observed RED. +- **acceptance_criteria**: Bounded RED distinguishes a real stop from forcing bookkeeping to STOPPED. Existing zero-component/already-stopped and slot-reuse controls remain; test does not rely on an unbounded sleep or fabricate state completion. +- **validation**: `python3 scripts/quality/run-native.py --task T29.0 --regex '^test_shutdown_coordinator$'`; retain exact failing observations. +- **status**: Completed (commits `f2b12822`, `7b5acd88`) +- **log**: 17 cases executed; three expected RED failures demonstrate false timeout success and spurious-wake completion. Production untouched until RED; actual completion and64slot-reuse controls retained. +- **files edited/created**: + +### T29.1: Report coordinator timeout without inventing stopped state +- **depends_on**: [T29.0] +- **agent_type**: worker +- **ownership**: `src/core/shutdown_coordinator.c`, corresponding contract comments in `include/core/shutdown_coordinator.h`, `quality/evidence/T29.1.md`. +- **description**: Honor existing wait contract: return false if the deadline expires before all live components stop, and preserve their true states. Wait in a predicate loop using the same absolute deadline so spurious/early condition signals cannot masquerade as successful shutdown. Preserve real completion notifications, already-stopped behavior and legitimate slot reuse; no caller/shutdown ordering redesign. +- **acceptance_criteria**: T29.0 reaches GREEN under ASan/UBSan; timeout stays bounded, live state is retained, real completion returns true and stopped slots can be reused. Main's existing warning branch becomes reachable. No new unconditional wait, forced kill, or persisted/API state change. +- **validation**: Focused coordinator sanitizer tests followed by T29 actual process lifecycle checks; forced cleanup must remain distinguishable from graceful completion. +- **status**: Completed (commits `4bb5cae3`, `c80872eb`) +- **log**: 17/17 coordinator sanitizer tests pass after observedRED. Wait uses original absolute deadline/predicate loop, returns false without inventing stopped state. Actual lifecycle normal stops3847/4240ms graceful; separate stale-media metadata defects remain under T29.2/T29.3. +- **files edited/created**: + +### T29.4: Produce previous-release and actual-process lifecycle evidence +- **depends_on**: [T3, T13, T29.1] +- **agent_type**: worker +- **ownership**: `tests/operational/lifecycle/` producer harness, `tests/fixtures/eng/upgrade/`, `tests/unit/cmake/eng/lifecycle.cmake`, `quality/evidence/T29.md`. +- **description**: Record historical synthetic fixture provenance and run owned actual recording, graceful restart, forced termination/recovery, DB/media integrity and browser preference checks. Preserve genuine incomplete-media reconciliation failures for T29.2/T29.3 instead of weakening or skipping them. +- **acceptance_criteria**: Exact previous tagged-source SQL/config/preference inputs and hashes; real nonempty assertions, bounded deadlines and verified-process cleanup. Normal shutdown success excludes forced kills; deliberately injected SIGKILL is separately reported. +- **validation**: Recorded T29 fixture checks and actual lifecycle runner commands. +- **status**: Completed — tests-only producer with two genuine failures (commit `0a811fe0`) +- **log**: Historical89migration sources verified;13 runtime assertions execute,11pass/twoRED. Normal stops3847/4240ms with noforcedcleanup. Corrupt40byte MP4 promoted complete and stale zero/missing metadata retained; bounded successors required. +- **files edited/created**: Producer paths recorded in commit0a811fe0 and quality/evidence/T29.md. + +### T29.2: Validate stale media before completion and guarantee scan progress +- **depends_on**: [T29.4] +- **agent_type**: worker +- **ownership**: `src/storage/storage_manager.c`, `src/database/db_recordings.c`, `include/database/db_recordings.h`, `tests/operational/lifecycle/test_incomplete_media_validation.c`, `tests/unit/cmake/eng/incomplete_media_validation.cmake`, `quality/evidence/T29.2.md`. +- **description**: Add focused tests for valid MP4, truncated nonempty MP4 and retained invalid entries spanning a500-row batch. Require usable local media before promoting stale incomplete rows; retain unreadable/corrupt evidence incomplete. Advance past retained rows through bounded query/progress semantics while preserving one-hour active-recording grace. Reuse existing probing where suitable; no damaged-file repair or deletion in this task. +- **acceptance_criteria**: RED reproduces false completion; GREEN leaves corrupt files unpromoted, finalizes valid stale media, preserves recent rows, reaches valid candidates behind at least500 retained invalid entries, and terminates within deadline without deleting files. Actual T29 truncated-media assertion passes after rebuild. +- **validation**: `python3 scripts/quality/run-native.py --task T29.2 --regex '^test_incomplete_media_validation$' --jobs 4`; existing recording DB tests and actual T29 scenario. +- **status**: Completed (production `8ad48404`, evidence `3a4bc326`) +- **log**: Tests-first decoded-video and bounded keyset scan controls pass44/44 Unity cases across3 ASan/UBSan targets. Real historical-upgrade/restart scenario now12/13, truncated-media assertion GREEN; only separate T29.3 phantom rows remain. Interruptible I/O/sample limits do not claim a decoder CPU deadline or full-file integrity. +- **files edited/created**: + +### T29.3: Prune confirmed stale phantom recordings without weakening active deletion guards +- **depends_on**: [T29.2, T28.1, T29.5] +- **agent_type**: worker +- **ownership**: `src/storage/storage_manager.c`, `src/storage/storage_deletion.c`, `include/storage/storage_deletion.h`, `tests/operational/lifecycle/test_stale_recording_prune.c`, `tests/unit/cmake/eng/stale_recording_prune.cmake`, `quality/evidence/T29.3.md`. +- **description**: Add tests-first coverage and narrow stale-recovery transaction for aged missing/zero-byte recordings on confirmed available storage. Recheck stale/incomplete identity, protection and migration safeguards within the transaction; prove no live writer owns the candidate. Preserve ordinary API rejection of incomplete recordings and retain rows when availability/ownership cannot be established. Age or failed stat alone is insufficient authorization to prune. Persist explicit expected missing/empty identity guards through T29.5 so delayed/restarted deletion refuses replacement or newly nonempty files. Conservatively require persisted disabled stream and no live continuous/MP4 writer; retain enabled or unknown stream candidates. Support legacy paths and explicitly attributed filesystem targets only: snapshot full target UUID/object key, require actual mount_guard_active/access checks and canonical existing root, resolve key to the exact stored path, compare target/key inside the claim transaction and again during queued processing. No sentinel guarantee is invented; nonfilesystem/unknown targets remain retained. +- **acceptance_criteria**: RED reproduces phantom rows; GREEN removes only confirmed stale missing/empty metadata with consistent deletion bookkeeping. Recent/live, protected, migrating, unavailable-root and changed-since-selection candidates remain intact; general active-writer deletion tests stayGREEN. Attributed empty/missing positive controls and wrong-key, unavailable/mismatched mount/root, changed-target/key and re-enabled/live-after-queue negative controls pass. Corrected full T29 lifecycle passes13/13. +- **validation**: `python3 scripts/quality/run-native.py --task T29.3 --regex '^test_(stale_recording_prune|storage_deletion_ledger)$' --jobs 4`; existing T28 retention controls and rebuilt T29 process scenario. +- **status**: Completed (fixes `922bb288`/`cef412d7`, evidence `25278303`) +- **log**: Tests-first delayed/reopened file and ownership guards plus attributed filesystem identity controls pass75/75 across5 ASan/UBSan targets; final focused36/36. Actual T29 now13/13, including automatic filesystem attribution, two confirmed phantom removals, retained corrupt control, schema90 and graceful shutdown. No S3/unknown relaxation; non-atomic external-writer and legacy mount limits documented. +- **files edited/created**: + +### T29.5: Persist stale-deletion identity guards across restart +- **depends_on**: [T29.4, T28.1] +- **agent_type**: local +- **ownership**: `db/migrations/0090_add_stale_deletion_guards.sql`, generated `include/database/db_embedded_migrations.h`, `tests/operational/lifecycle/test_stale_guard_migration.py`, current-schema expectation only in `tests/operational/lifecycle/run.mjs`, `quality/evidence/T29.5.md`. +- **description**: Add explicit nullable stale_guard_kind/dev/ino fields to the durable deletion-object ledger so T29.3 can distinguish expected absence from a specific empty inode after a restart. Ordinary deletion objects remain NULL/unmodified. Keep the historical89-source fixture immutable; require those prior versions/data plus the explicit new migration in the actual upgrade check. Use the repository embedded-migration generator; no hand-edited generated arrays. +- **acceptance_criteria**: Tests first cover missing guard schema, then actual historical-to-current SQL upgrade preserves existing rows and gives NULL guard defaults. Explicit missing/empty guard values survive reopening and invalid guard shapes fail or are rejected by the consumer. Migration/header check passes. Never silently remove pending safety guards on downgrade; unsupported rollback must fail explicitly or reject pending guarded operations, with honest documented limits. No production database is touched. +- **validation**: Focused SQLite migration tests using disposable databases, `python3 tests/unit/test_embedded_migration_generator.py`, generator --check, and rebuilt actual T29 upgrade/restart assertions after T29.3. +- **status**: Completed (schema `99a29f66`, evidence `32e7adb5`) +- **log**: Four real SQLite tests pass after missing-column RED; historical rows/defaultNULL preserved, guard values persist across reopen, invalid shapes rejected and downgrade explicitly unsupported. Generator4tests/--check pass. Real lifecycle schema continuity now verifies90versions, preserving historical89fixture; separate stale attribution failure remains T29.3. +- **files edited/created**: + +## Advisory regression coverage tasks (T7.2) + +### T7.2.1: Verify external migration trust boundaries +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `tests/unit/test_t7_2_migrations.c`, `tests/unit/cmake/eng/t7_2_migrations.cmake`, `quality/evidence/T7.2.1.md`; production read-only. +- **description**: Exercise actual disposable SQLite migrations through public migrate_init/up/down: valid SQL succeeds, external symlink/prefix escape and world-writable files never execute, rejected SQL cannot attach/write an outside fixture, and rollback applies the same file policy. Read src/database/sqlite_migrate.c and its public header; no production edits. +- **acceptance_criteria**: Allowed and rejected controls execute the real parser/SQLite path with precise schema/file effects. Preserve failure evidence and request a separately scoped fix if any boundary fails. +- **validation**: `python3 scripts/quality/run-native.py --task T7.2.1 --profile integration --regex '^test_t7_2_migrations$' --jobs 4`. Existing-fix verification can initially pass; do not fabricate a production RED or modify requirements to hide a failure. +- **status**: Completed — tests-only existing-fix verification +- **log**: Seven actual migration/SQLite/filesystem boundary cases pass integration ASan/UBSan, using mutable T7.7 cache (native-c9393ff3c3ba). Valid apply/rollback, symlink/prefix traversal, world-write mode, post-discovery replacement and disallowed ATTACH verified; no production edit or fabricated RED. Concurrent arbitrary filesystem mutation is not proven atomic. +- **files edited/created**: + +### T7.2.2: Verify advisory buffer truncation boundaries +- **depends_on**: [T7, T13, T7.2.5, T7.2.6, T7.2.7] +- **agent_type**: worker +- **ownership**: `tests/unit/test_t7_2_buffers.c`, `tests/unit/cmake/eng/t7_2_buffers.cmake`, `quality/evidence/T7.2.2.md`; production read-only. +- **description**: Exercise current query builder, polygon serialization through public DB calls, and multi-source go2rtc URL construction with synthetic upstream capture. Cover valid boundary sizes and oversized inputs; no external camera/network. Read src/database/db_query_builder.c, src/database/db_zones.c, src/video/go2rtc/go2rtc_api.c and existing tests. No production edits. +- **acceptance_criteria**: ASan/UBSan controls verify valid outputs and rejection or safe documented truncation without buffer overflow; no HTTP request may silently target a changed source identity. +- **validation**: `python3 scripts/quality/run-native.py --task T7.2.2 --profile integration --regex '^test_t7_2_buffers$' --jobs 4`. Existing-fix verification can initially pass; do not fabricate a production RED or modify requirements to hide a failure. +- **status**: Completed — original regressions resolved by T7.2.5–T7.2.7 +- **log**: Original17assertions preserved; expanded24/24 buffer cases pass, plus30related cases (54total), with2libyaml-unavailable controls explicitly ignored. Initial6RED retained; no fresh scanner-clean claim. +- **files edited/created**: + +### T7.2.3: Verify descriptor and filesystem safety at advisory sites +- **depends_on**: [T7, T13, T7.2.8] +- **agent_type**: worker +- **ownership**: `tests/unit/test_t7_2_files.c`, `tests/unit/cmake/eng/t7_2_files.cmake`, `quality/evidence/T7.2.3.md`; production read-only. +- **description**: Trace current successors of advisory daemon/backup/storage/FFmpeg/HLS filesystem sites. Produce bounded disposable-file tests for descriptor chmod and deletion/symlink behavior, reusing existing exact coverage where available. Public API/link wrappers only where necessary; no daemonization, real mounts or production edits. Split distinct discovered gaps before fixes. +- **acceptance_criteria**: Record an individual disposition for every original filesystem site, with real-file controls and explicit race limitations. Static absence of an old path does not prove its replacement safe. +- **validation**: `python3 scripts/quality/run-native.py --task T7.2.3 --profile integration --regex '^test_t7_2_files$' --jobs 4`. Existing-fix verification can initially pass; do not fabricate a production RED or modify requirements to hide a failure. +- **status**: Completed (producer `741beec3`, fix `e3ec7ea6`) +- **log**: All14real filesystem/descriptor cases pass after isolated fd0 regression fix;110total related ASan/UBSan cases, zero ignored. Individual original25–38sites mapped with explicit traversal/race limits. +- **files edited/created**: + +### T7.2.4: Verify settings and system operations avoid shell injection +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `tests/unit/test_t7_2_settings.c`, `tests/unit/cmake/eng/t7_2_settings.cmake`, `quality/evidence/T7.2.4.md`; production read-only. +- **description**: Read current settings path validation and system directory/process operations replacing the advisory shell sites. Exercise public settings handlers with temporary accepted/rejected paths and assert persistence/side effects. Verify system inspection invokes no shell using wrappers or direct execution evidence. No production edits. +- **acceptance_criteria**: Valid settings remain compatible; unsafe path inputs cannot cause shell execution or unwanted persistence. Source-only null-check cleanup is classified separately from a runtime defect. +- **validation**: `python3 scripts/quality/run-native.py --task T7.2.4 --profile integration --regex '^test_t7_2_settings$' --jobs 4`. Existing-fix verification can initially pass; do not fabricate a production RED or modify requirements to hide a failure. +- **status**: Completed — tests-only existing-fix verification +- **log**: Seven settings/system/real-statvfs cases pass ASan/UBSan using mutableT7.7cache; exact artifacts/source hashes retained. No shell calls or host config writes, optional go2rtc version unavailable without network. +- **files edited/created**: + + +## Bounded advisory buffer fixes (T7.2.2) + +### T7.2.5: Reject incomplete query column construction +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/database/db_query_builder.c`, `tests/unit/test_t7_2_buffers.c` (additional boundary controls only; preserve existing assertions), `quality/evidence/T7.2.5.md`. +- **description**: Return explicit failure when a selected column or separator cannot fit, preserving valid query/index behavior. Existing public64×63-character column regression must stop returning an incomplete SELECT. Tests-first producer ff7cc356 is already committed; no broad refactor or unrelated source ownership. +- **acceptance_criteria**: Existing query overflow RED becomesGREEN; valid long clauses and max-fitting query controls remain passing. Keep unrelated known buffer failures visible. +- **validation**: `python3 scripts/quality/run-native.py --task T7.2.5 --profile integration --regex '^test_t7_2_buffers$' --jobs 4`; record per-cause outcomes while other explicitly assigned failures remain, then final allGREEN plus relevant existing DB/go2rtc controls. +- **status**: Completed (commit `17b6536a`) +- **log**: Two overflow branches now return explicit failure. All6query controls pass; full producer12PASS/5knownRED until separate zone/URL fixes. Baseline/advisory metadata unchanged. +- **files edited/created**: + +### T7.2.6: Reject polygon counts beyond fixed zone capacity +- **depends_on**: [T7.2.5] +- **agent_type**: worker +- **ownership**: `src/database/db_zones.c`, `tests/unit/test_t7_2_buffers.c` (additional boundary controls only; preserve existing assertions), `quality/evidence/T7.2.6.md`. +- **description**: Validate count against MAX_ZONE_POINTS before reading the fixed polygon array or mutating persisted zones. Preserve valid32-point polygons and transactional rollback on serialization failure. Tests-first producer ff7cc356 is already committed; no broad refactor or unrelated source ownership. +- **acceptance_criteria**: Existing33-point RED becomesGREEN, negative/zero/oversized counts are rejected consistently with documented valid polygon rules and prior rows survive failure; valid maximum and output-overflow rollback controls pass. +- **validation**: `python3 scripts/quality/run-native.py --task T7.2.6 --profile integration --regex '^test_t7_2_buffers$' --jobs 4`; record per-cause outcomes while other explicitly assigned failures remain, then final allGREEN plus relevant existing DB/go2rtc controls. +- **status**: Completed (commit `5239b965`) +- **log**: Prevalidate all polygons3..MAX_ZONE_POINTS before transaction; valid32-point and zero-zone-list clearing preserved. Seven zone controls pass, including invalid later batch with no SQLite changes. +- **files edited/created**: + +### T7.2.7: Preserve complete go2rtc multi-source identities or reject before transport +- **depends_on**: [T7.2.6] +- **agent_type**: worker +- **ownership**: `src/video/go2rtc/go2rtc_api.c`, `tests/unit/test_t7_2_buffers.c` (additional boundary controls only; preserve existing assertions), `tests/unit/cmake/eng/t7_2_buffers.cmake` (targeted malloc wrapper registration only), `quality/evidence/T7.2.7.md`. +- **description**: Fix checked encoding/URL sizing and error paths in the multi-source request builder only. Reject any source/name or aggregate overflow before curl perform, or allocate complete checked encodings. Preserve valid request semantics and free all allocations. Do not silently drop a source or truncate identity; check arithmetic/count constraints before allocation/iteration. Tests-first producer ff7cc356 is already committed; no broad refactor or unrelated source ownership. +- **acceptance_criteria**: All four captured identity/overflow RED cases and valid request controls pass; final17-case producer suite is entirelyGREEN with ASan/UBSan. Additional boundary tests may strengthen coverage but original assertions cannot be weakened. +- **validation**: `python3 scripts/quality/run-native.py --task T7.2.7 --profile integration --regex '^test_t7_2_buffers$' --jobs 4`; record per-cause outcomes while other explicitly assigned failures remain, then final allGREEN plus relevant existing DB/go2rtc controls. +- **status**: Completed (commit `e9f4e639`) +- **log**: Complete checked curl escaping and aggregate sizing reject overflow before transport; preserve more than4sources and clean up allocations.24/24 buffer cases and30related native cases pass ASan/UBSan;2libyaml-unavailable controls ignored explicitly. +- **files edited/created**: + + +### T7.2.8: Initialize skipped filesystem descriptor ownership +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/core/path_utils.c` _chmod_path output descriptor initialization only, `quality/evidence/T7.2.8.md`; preserve T7.2.3 producer assertions. +- **description**: Initialize a supplied fd_out to -1 before any early return, including the successful ELOOP symlink skip. Preserve successful directory descriptor transfer and regular-file cleanup. No broader traversal rewrite. +- **acceptance_criteria**: Deterministic permitted-value regression no longer consumes unrelated private fd0; ordinary descriptor-based permission, symlink skip and cleanup controls remain GREEN. Explicitly distinguish test-only zero initialization from production compiler behavior. +- **validation**: Existing T7.2.3 13-case producer under ASan/UBSan plus related filesystem/backup/HLS/deletion controls. No claim of atomic arbitrary concurrent path traversal or sanitizer detection of uninitialized reads. +- **status**: Completed (commit `e3ec7ea6`) +- **log**: Initialize supplied fd_out=-1 before early returns; isolated skipped-symlink fd0 regression reachesGREEN.14producer and96related cases pass ASan/UBSan, zero ignored; production compiler flags unchanged. +- **files edited/created**: + +## Credential storage contract follow-up (T7.11) + +### T7.11.1: Propose the destination credential storage and migration contract +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `quality/credential-storage-contract.json`, `quality/evidence/T7.11.1.md`; application/schema/deployments read-only. +- **description**: Produce a concrete reviewable design from reproduced destination plaintext persistence. Define threat model, supported unattended runtime/key-provider contract, provisioning and missing-key behavior, rotation, transactional migration, historical DB/WAL/backup exposure and downgrade/restore policy. Inspect current deployment and runtime constraints; record deferred physical/non-container questions rather than pretending answered. Prefer a bounded compatible design and explicitly state unresolved operational decisions. Do not implement a speculative key scheme or approve exposure. +- **acceptance_criteria**: Exact proposed key/data lifetimes and failure semantics, compatibility limits, migration/rollback validation matrix and bounded implementation ownership/dependency proposal. No hardcoded key, reversible encoding presented as encryption, secret logging or silent plaintext fallback. Existing runtime credential recovery/rotation and API omit/clear semantics must remain expressible. The proposal alone cannot close T7.11/T33; approved exception or verified implementation is still required. +- **validation**: Read-only source/deployment trace, JSON validity and concrete scenario/ownership review. Design work has no executable product RED/GREEN cycle; state reason_not_testable. Any cryptographic standard/library claim requires authoritative verification. +- **status**: Completed — proposal only (`e0e5c9e3`, `cc3a6c7e`) +- **log**: Concrete external-keyring/AEAD/migration/backup/restore proposal,17source hashes and primary references,9unmaterialized implementation children and12validation scenarios. All5operational decisions unapproved/null-owner; no implementation, schema reservation, exposure approval or parentT7.11closure. +- **files edited/created**: + +## Authorization fixture follow-up (T7.53) + +### T7.53.1: Give authorization deletion tests an owned available storage root +- **depends_on**: [T7, T13, T28.1] +- **agent_type**: worker +- **ownership**: `tests/unit/test_authorization.c` sensitive-handler storage fixture setup/paths/teardown and response-helper failure cleanup only, `quality/evidence/T7.53.1.md`; production read-only. +- **description**: The existing sensitive-handler test uses fixed /tmp recording paths while its configured legacy root is unavailable/unknown, so the durable deletion guard correctly queues work and returns202 instead of expected immediate200. Establish an owned available root and move only synthetic recording paths below it, restore configuration and remove fixture on success/failure. Keep expected200 and deleted metadata for the healthy-root case, denied403 and retained metadata for unauthorized requests. Free HTTP response on status assertion failure. Confirm source reason before edits; if another real product issue appears preserve RED and split it. +- **acceptance_criteria**: Original34authorization cases pass with precise existing status/metadata/deny assertions intact; no200-or202 shortcut or weakened root protection. Private fixtures clean up and no leak remains on the formerly failing assertion path. Production code unchanged. +- **validation**: Existing test_authorization plus T7.53 dedicated cache regressions under ASan/UBSan. Prior failure reproduced with pre-change authorization.c at native-2d47cc11c188; do not attribute fixture repair to the arithmetic guard. +- **status**: Completed (commit `230c6767`) +- **log**: Owned available legacy root and synthetic paths preserve exact200/403/202 plus metadata assertions. Response cleanup precedes failing assertion;34authorization and10focused cases pass ASan/UBSan, no ignored/leaks; private root removed and config restored. +- **files edited/created**: + +## Additional security boundary work (T7.24/T7.25) + +### T7.24.1: Verify pinned FAAC sample layout and channel boundaries +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `tests/unit/test_t7_24.c`, `tests/unit/cmake/eng/t7_24.cmake`, `quality/evidence/T7.24.1.md`; upstream/application source read-only. +- **description**: Test the actually linked pinned FAAC implementation through its public interface: packed int16 mono/stereo/eight-channel signed samples versus equivalent int32×256 input, valid channel maps and invalid indexes, partial/full-frame/redzone boundaries, null/flush/oversized/undersized-output rejection and unchanged input/close ownership. No mock codec or upstream edits. +- **acceptance_criteria**: Exact meaningful sample/layout behavior and sanitizer controls, preserving any real failure. Existing configuration-only tests cannot substitute. Final-stride pointer formation may escape ASan detection; do not infer absence of C pointer-formation UB or approve scanner dismissal from encoded-output equivalence alone. +- **validation**: `python3 scripts/quality/run-native.py --task T7.24.1 --profile integration --regex '^test_(t7_24|faac_encoder)$' --jobs 4`; any upstream patch/pin requires a separate tracked integration child. +- **status**: Completed — tests-only producer (`b82307d8`) +- **log**: 10new plus2existing actualFAAC cases pass ASan/UBSan, exact int16/int32layout equivalence, maps/alignment/boundary/ownership. Initial unsupported immediate-empty-flush oracle corrected to documented boundeddrain; no productfix or staticpointerformation safety claim. +- **files edited/created**: + +### T7.25.1: Quarantine and verify stale backup artifacts before unlinking +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/database/db_core.c` cleanup_stale_backup_artifacts and narrowly scoped helpers only, `tests/unit/test_t7_25.c`, `tests/unit/cmake/eng/t7_25.cmake` needed fault seams only, `quality/evidence/T7.25.1.md`; no schema/retention/config redesign. +- **description**: Resolve committed parent/leaf replacement regressions using held backup directory and candidate descriptors plus an owned private directory for atomic quarantine. Pin regular candidate without following symlinks or blocking on special files; verify current type/name eligibility/identity and snapshot size/mtime, preserving the existing startup-only cleanup policy without inventing an age cutoff. Move relative to held directory, then compare held inode with quarantined entry before deletion. Restore mismatches only with atomic no-overwrite semantics; on conflict/error/unsupported restore preserve the quarantined object and report its retained location. Verify private directory ownership/mode/identity and use descriptor-relative operations. Never recursively delete unknown leftover quarantine after crash. +- **acceptance_criteria**: Original inspected-parent replacement preserves replacement bytes and removes only original eligible artifact; symlink and regular leaf replacements survive with target bytes intact. Cover restore collision, failed rename/unlink/open, inode pinning and descriptor/private-directory cleanup. Unsupported no-overwrite restore must preserve data and report incomplete cleanup, never fall back to racy overwrite. Document crash leftovers, old-kernel/platform limits and exclusion of a compromised runtime UID/privileged actor or concurrent writes through an already-held fd. Ordinary selective stale cleanup remains functional. +- **validation**: Original7-case producer (3genuineRED) retained, strengthened fault/crash/ownership controls and related backup/database tests under ASan/UBSan. Native source/build lease required; no real backup directory, database or process touched. +- **status**: Completed (commit `54d2f7ca`) +- **log**: Original3RED races resolved via helddescriptors/privatequarantine and guardedSYS_renameat2 NOREPLACE for both moves.29focused+2DB+14filesystem=45/45 ASan/UBSan pass; repeatedentry and populated-directory collision regressions retained. Unsupported operations preserve originals/retainedobjects; no recursive crashcleanup, documented empty-directory metadata race/privilegedwriter limits. +- **files edited/created**: + +## Reproducible FAAC correction and analysis provenance (T7.24) + +### T7.24.2: Apply a tracked FAAC input-pointer correction in a verified build copy +- **depends_on**: [T7.24.1] +- **agent_type**: worker +- **ownership**: `CMakeLists.txt` FAAC source preparation only, `cmake/PrepareFaac.cmake`, `patches/faac/0001-bound-input-pointer-arithmetic.patch`, `patches/faac/manifest.json`, `patches/faac/manifest.cmake` (literal equivalent hash tables with JSON identity), `tests/quality/test_faac_patch.py`, `tests/unit/test_t7_24.c` additional format controls only, `quality/evidence/T7.24.2.md`. +- **description**: Implement the reviewed three-file tracked patch (frame.c/faac.c/faac_internal.h): const void transport and format-correct bounded indexed16/32/float reads, preserving numeric scaling/float screening/packed24/publicABI. Verify pinned gitlink and all copied input hashes, patch hash/path scope and changed postimages. Copy a consistent FAAC source/header tree into owned build-local staging, apply only committed patch and publish verified output/provenance. Keep submodule pristine and license intact; no unpublished gitlink, network at configure, source filter exclusion or #line path disguise. Preserve declared minimum build-tool compatibility; request a separate bounded toolchain change if required. +- **acceptance_criteria**: Tests-first failure controls for input/pin/patch/postimage drift, path escape/symlink and stale/tampered output; deterministic successful regeneration and no mixed source/header roots. Generated manifest maps exact compiled paths to canonical upstream identity and hashes. Static index proof eliminates unused terminal pointer and unnecessary int32 conversion; current semantic regressions may initially pass and must not be fabricated as RED. Actual format/AAC/media sanitizer controls pass. Scanner qualification remains separately fenced byT7.24.3. +- **validation**: `python3 -m unittest discover -s tests/quality -p test_faac_patch.py`; `python3 scripts/quality/run-native.py --task T7.24.2 --profile integration --regex '^test_(t7_24|faac_encoder|mp4_(audio_recovery|segment_boundaries|segment_timestamps|faststart|pressure_reconnect))$' --jobs 4`. +- **status**: Completed +- **log**: 9d845904 verified tracked three-file patch preserves public ABI/formats and bounds indexed reads. Twenty compiled units/49 copied inputs verified; 24 preparation tests and 36 native cases across seven targets pass. Reviewed minimum-CMake implementation and unchanged gitlink. Actual scanner qualification remains T7.24.3/T33. +- **files edited/created**: + +### T7.24.3: Require compiled FAAC patch provenance in scanner coverage +- **depends_on**: [T7.24.2, T14.1] +- **agent_type**: worker +- **ownership**: `.github/workflows/codeql-analysis.yml`, `.github/workflows/static-analysis.yml`, `scripts/quality/scanner_run.py`, `tests/quality/test_scanner_ci.py`, `quality/evidence/T7.24.3.md`. +- **description**: Extend existing localT14 wiring0901a9d2 and completedT14.1 manifest engine. Preserve all prior src coverage and add actual compiled patched FAAC translation units to native scanners. Require/hash-check generated source/patch mapping and actual CodeQL archive entries; archive manifests and compile commands with reports. Preserve raw generated paths; original finding reconciliation is evidence-backed T33 work, never baseline rewrite or path suppression. Depend on local provenance producer, not pending hostedT14acceptance, to permit independent implementation. +- **acceptance_criteria**: Missing generated/archive file, hash mismatch, stale pin/patch, mixed roots or omitted actual invocation fail closed; valid source provenance retains original and compiled identities. Clang/cppcheck include previoussrc and actualcompiledFAACunits; unavailable tools/archive remain explicit coverage failures. Synthetic tests cannot substitute for real scanner execution. Local integration may finish while hosted/full-profile qualification stays open underT14/T33; parentT7.24 remains open until required actual evidence exists. +- **validation**: Tests-first `python3 -m unittest discover -s tests/quality -p test_scanner_ci.py`; actionlint; available actual native/CodeQL extraction with preserved manifests and hashes. No remote push/dispatch implied. +- **status**: Completed locally (`937d4834`); actual scanner qualification pending +- **log**: Reviewed raw-path provenance, actual compiler inputs and scanner-specific prior coverage union.43 tests pass after17 new RED failures/errors; actual49-file/20-unit build metadata validates. Tools unavailable, no actual scan or parent closure claimed. Whole quality suite now89Node+158Python pass; T14/T33 remain open. Follow-up locally unpacked clang21/cppcheck2.19 each analyzes all20codecunits;104clang and30cppstyle/warning diagnostics plus15coverage limits retained, not clean/fullcoverage. CodeQL still unavailable. +- **files edited/created**: + +## Published-backup pruning identity verification (T7.25) + +### T7.25.2: Reproduce published-backup retention replacement boundaries +- **depends_on**: [T7, T13, T7.25.1] +- **agent_type**: worker +- **ownership**: `tests/unit/test_t7_25_retention.c`, `tests/unit/cmake/eng/t7_25_retention.cmake`, `quality/evidence/T7.25.2.md`; production read-only. +- **description**: Separately investigate prune_timestamped_backups, which still stores pathnames after stat and later unlinks them. Use disposable timestamped backup files and deterministic parent/leaf replacement after inspection; require unchanged replacement data and correct ordinary retention selection. This is not the already-fixed stale temporary-artifact helper. No real DB/backups, symlink target mutation or arbitrary large allocations. Produce bounded follow-up source ownership before any fix. +- **acceptance_criteria**: Real filesystem controls verify normal retention count/order, exclusion of temporary artifacts, parent replacement, changed leaf and symlink handling, with exact retained bytes/inodes. Preserve any genuine RED. Tests-only completion does not assert security fix; materialize separate correction before T33 can close if hypothesis confirmed. +- **validation**: `python3 scripts/quality/run-native.py --task T7.25.2 --regex '^test_t7_25_retention$' --jobs 4`; source/native lease follows active FAAC integration and storage-migration producer. +- **status**: Completed — tests-only RED producer +- **log**: Five ASan/UBSan cases: two normal controls pass, three parent/leaf replacement races fail. Full evidence and source hashes retained; no production change. Correction remains fenced separately. +- **files edited/created**: + + +### T7.25.3: Preserve selected published backup identity during retention +- **depends_on**: [T7.25.2, T7.25.1] +- **agent_type**: worker +- **ownership**: `src/database/db_core.c` prune_timestamped_backups and narrowly shared identity helpers only, `tests/unit/test_t7_25_retention.c`, `tests/unit/cmake/eng/t7_25_retention.cmake`, `quality/evidence/T7.25.3.md`. +- **description**: Resolve the three committed retention replacement failures. Hold the inspected directory and selected regular-file identities from collection through deletion. Reuse bounded private quarantine/no-overwrite restore where appropriate, without repinning a changed leaf as the original candidate. Preserve newest-first retention count/order and exclusion of incomplete artifacts. No age/config/schema redesign or recursive cleanup of unknown leftovers. +- **acceptance_criteria**: Original two controls and three race assertions remain; deterministic seams may follow descriptor-relative inspection while asserting injection. Changed regular/symlink entries and target bytes survive; original-directory pruning survives pathname replacement. Unsupported operations and cleanup failures preserve objects and report incomplete pruning. Add failure/descriptor-cleanup controls and preserve all stale-artifact regressions. Document privileged-writer limits. +- **validation**: Committed tests-first producer877f9e5e; focused retention, stale-artifact and database regressions under ASan/UBSan with full logs and source hashes. Native/source lease follows storage migration corrections; no server build or real backups. +- **status**: Completed (`43339ef0`) +- **log**: Held selected inodes and original directory through quarantine/removal; actual resource failures preserve all files before deletion. Original12/17 RED plus duplicate-name review RED retained. Final19retention+29stale+2DB+14filesystem=64/64 sanitizer cases pass; hard-linked filename semantics preserved, no fixed count cap. +- **files edited/created**: + +## Storage migration identity corrections (T7.26) + +### T7.26.1: Publish storage migration files without overwriting competing entries +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/storage/storage_migration.c` bounded cause only, `tests/unit/test_t7_26.c`, `tests/unit/cmake/eng/t7_26.cmake` needed fault seams only, `quality/evidence/T7.26.1.md`. +- **description**: Resolve CODEQL-310 with atomic no-overwrite publication. Preserve regular and symlink destinations inserted after the final absence check, source bytes and recording location metadata. Unsupported atomic operations fail without replacing another entry; preserve normal move/copy. Keep separate cleanup and verified-temporary RED cases visible until their ordered children. +- **acceptance_criteria**: Preserve committed producer assertions and normal controls; add focused unsupported-operation, identity and cleanup failure cases as appropriate. No real storage/database/process touched, no unrelated schema or policy redesign. Document platform and privileged-writer limits honestly. Fresh scanner reconciliation remains T33. +- **validation**: Tests-first producer `dc571048`; `python3 scripts/quality/run-native.py --task T7.26 --regex '^test_t7_26$' --jobs 4`, related migration tests under ASan/UBSan, exact source/artifact provenance. Native source/build lease required. +- **status**: Completed (`87b812ad`) +- **log**: Atomic guardedSYS_renameat2 no-overwrite publication resolves both competing-entry races. Eighteen cases pass with two separate known RED causes retained for .2/.3; normal migration9/9 pass, unsupported/error controls pass and sanitizers clean. Scope/source diff reviewed. +- **files edited/created**: + +### T7.26.2: Retain abandoned migration destinations without proven ownership +- **depends_on**: [T7.26.1] +- **agent_type**: worker +- **ownership**: `src/storage/storage_migration.c` bounded cause only, `tests/unit/test_t7_26.c`, `tests/unit/cmake/eng/t7_26.cmake` needed fault seams only, `quality/evidence/T7.26.2.md`. +- **description**: Resolve the independent abandoned-cleanup cause: a failed job must never delete an unrelated final destination it did not publish. Without persistent ownership proof, retain/report the object conservatively. Preserve cancellation/job transition behavior; do not invent an ownership assertion or broaden schema/storage policy. +- **acceptance_criteria**: Preserve committed producer assertions and normal controls; add focused unsupported-operation, identity and cleanup failure cases as appropriate. No real storage/database/process touched, no unrelated schema or policy redesign. Document platform and privileged-writer limits honestly. Fresh scanner reconciliation remains T33. +- **validation**: Tests-first producer `dc571048`; `python3 scripts/quality/run-native.py --task T7.26 --regex '^test_t7_26$' --jobs 4`, related migration tests under ASan/UBSan, exact source/artifact provenance. Native source/build lease required. +- **status**: Completed (`e3321d58`) +- **log**: Conservatively retains filesystem final destinations when failed/cancelled jobs lack ownership proof, including matching bytes and inspection errors. Twenty-four pass, only separate verified-temp substitution RED remains; sanitizers clean. Reviewed source diff and persisted explanatory cleanup error; temporary ownership still separate. +- **files edited/created**: + +### T7.26.4: Preserve preexisting migration temporary files and cleanup ownership +- **depends_on**: [T7.26.2] +- **agent_type**: worker +- **ownership**: `src/storage/storage_migration.c` temporary creation and abandoned temporary cleanup only, `tests/unit/test_t7_26.c`, `tests/unit/cmake/eng/t7_26.cmake` required seams only, `quality/evidence/T7.26.4.md`. +- **description**: Commit the separately observed preexisting-part RED producer, then prevent truncation and later cleanup deletion of temporary names without ownership proof. Creation must be exclusive or use a fresh owned namespace; pathname derived from a job UUID alone is not persistent proof of ownership. Preserve unknown regular files/symlinks and report cleanup debt conservatively. This is separate from final publication overwrite and verified temporary substitution. +- **acceptance_criteria**: Preexisting temporary sentinel bytes survive both the first attempt and later abandoned cleanup; symlink targets survive. Preserve normal move/copy and explicit cancellation/failure behavior. A failed attempt must not silently claim cleanup success. Document retry handling for retained artifacts; avoid permanently wedging ordinary transient-failure retries by assuming unknown existing bytes are owned. Never delete unknown artifacts merely to resume. No schema/storage policy redesign or unrelated S3 change. +- **validation**: Preserve native-87375c6a8757 producer18cases/3RED (two new causes and known .3). Tests-first focused and related migration tests under ASan/UBSan; exact source hashes/full logs, private disposable storage only. .3 remains RED until its later implementation. +- **status**: Completed (`d829c053`; RED producer `787c33e7`) +- **log**: Preexisting temporary bytes are preserved using bounded exclusive fresh-attempt creation; retries progress without reclaiming old unknown bytes. Conservative abandoned-prefix inspection records unresolved legacy/randomized cleanup debt. Twenty-nine cases pass and only separate .3 substitution remains RED; no sanitizer diagnostics. Exact new-producer success-via-distinct-artifact contract recorded before implementation. +- **files edited/created**: + +### T7.26.3: Preserve verified migration temporary identity through publication +- **depends_on**: [T7.26.4] +- **agent_type**: worker +- **ownership**: `src/storage/storage_migration.c` bounded cause only, `tests/unit/test_t7_26.c`, `tests/unit/cmake/eng/t7_26.cmake` needed fault seams only, `quality/evidence/T7.26.3.md`. +- **description**: Keep the verified temporary identity through publication and metadata commit. Reject the committed substitution producer before recording location changes; preserve replaced and verified objects. Hold descriptors and use an owned private namespace as necessary; a second pathname check alone is insufficient. Preserve normal move/copy, checksum, source deletion and cleanup semantics. Any alternative that publishes held verified bytes requires an explicit revised contract before implementation, never unverified data. +- **acceptance_criteria**: Preserve committed producer assertions and normal controls; add focused unsupported-operation, identity and cleanup failure cases as appropriate. No real storage/database/process touched, no unrelated schema or policy redesign. Document platform and privileged-writer limits honestly. Fresh scanner reconciliation remains T33. +- **validation**: Tests-first producer `dc571048`; `python3 scripts/quality/run-native.py --task T7.26 --regex '^test_t7_26$' --jobs 4`, related migration tests under ASan/UBSan, exact source/artifact provenance. Native source/build lease required. +- **status**: Completed (`eeeb1c5e`) +- **log**: Held verified descriptor, private quarantine and precommit identity snapshot resolve original and added replacement races; cancellation/commit failure preserve unknown entries. Final28focused+9existing=37/37 pass, sanitizers clean. Documented final filesystem/SQLite window, open-writer and logged-only successful empty-directory debt; source diff reviewed. +- **files edited/created**: + + +## Floor-plan background ownership corrections (T7.27) + +### T7.27.1: Preserve unknown floor-plan backup artifacts +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_operator_floor_plans.c` background PUT and narrowly related file helpers only, `tests/unit/test_t7_27.c`, `tests/unit/cmake/eng/t7_27.cmake` required seams only, `quality/evidence/T7.27.1.md`. +- **description**: Remove the assumption that a predictable .bak pathname is owned. Preserve unrelated preexisting files/symlinks and use an exclusively owned backup location for this attempt. Ordinary upload/replacement and existing rollback behavior must continue; no deletion of unknown leftovers to make room. +- **acceptance_criteria**: Preserve committed competitor/source-byte, response and metadata assertions. A private backup namespace may replace legacy .bak observation in the fixture only while proving the same original bytes/inode survive and the competing public entry remains unchanged; document this observation change before implementation. No blanket overwrite/unlink cleanup, unrelated GET/DELETE redesign, schema changes or universal concurrent-writer claim. Document filesystem/SQLite and privileged/open-writer limits. No real user storage touched. +- **validation**: Tests-first producer183bdafe: nine focused cases/fiveRED plus eight passing API/DB controls. Add focused collision/unsupported/failure controls and run related cases under ASan/UBSan; preserve full logs and exact hashes. Sequential native/source/CMake lease required. +- **status**: Completed (`dc287459`) +- **log**: Verified private per-attempt backup directory preserves legacy .bak files/symlinks. Creation/validation cleanup fault controls pass; sixteen passing cases and four known later-child RED cases retained, sanitizers clean. Source ownership changes reviewed. +- **files edited/created**: + +### T7.27.2: Bind the inspected old floor-plan background identity +- **depends_on**: [T7.27.1] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_operator_floor_plans.c` background PUT and narrowly related file helpers only, `tests/unit/test_t7_27.c`, `tests/unit/cmake/eng/t7_27.cmake` required seams only, `quality/evidence/T7.27.2.md`. +- **description**: Resolve CODEQL-313 using a held old-file identity and validated private backup binding. Regular or symlink substitution after inspection must preserve substituted entries/targets and reject the operation without changing metadata. Hold the inspected directory as appropriate; do not repin a changed name as the original. Preserve legitimate initial upload, same-type replacement and failure recovery. +- **acceptance_criteria**: Preserve committed competitor/source-byte, response and metadata assertions. A private backup namespace may replace legacy .bak observation in the fixture only while proving the same original bytes/inode survive and the competing public entry remains unchanged; document this observation change before implementation. No blanket overwrite/unlink cleanup, unrelated GET/DELETE redesign, schema changes or universal concurrent-writer claim. Document filesystem/SQLite and privileged/open-writer limits. No real user storage touched. +- **validation**: Tests-first producer183bdafe: nine focused cases/fiveRED plus eight passing API/DB controls. Add focused collision/unsupported/failure controls and run related cases under ASan/UBSan; preserve full logs and exact hashes. Sequential native/source/CMake lease required. +- **status**: Completed (`a8ce89a3`) +- **log**: Held no-follow old-file inspection and atomic private binding reject regular/symlink substitution; failed mismatch restoration preserves entries. Twenty-two cases pass with two separate rollback RED cases; sanitizers clean. Unsupported pinning hardening is included in the following .3 review. +- **files edited/created**: + +### T7.27.3: Restore floor-plan backgrounds without overwriting competitors +- **depends_on**: [T7.27.2] +- **agent_type**: worker +- **ownership**: `src/web/api_handlers_operator_floor_plans.c` background PUT and narrowly related file helpers only, `tests/unit/test_t7_27.c`, `tests/unit/cmake/eng/t7_27.cmake` required seams only, `quality/evidence/T7.27.3.md`. +- **description**: Resolve CODEQL-314 and CODEQL-315 with no-overwrite restoration and identity-aware removal of this request's published upload on failure. Preserve competing public entries, the original backup and any displaced verified upload. Publication into the vacated public name must also avoid overwriting a new competitor; add a separate tests-first control for that boundary. Restore ordinary failure cases when no conflict exists; retain/report owned backup artifacts on restoration conflict or unsupported operations. +- **acceptance_criteria**: Preserve committed competitor/source-byte, response and metadata assertions. A private backup namespace may replace legacy .bak observation in the fixture only while proving the same original bytes/inode survive and the competing public entry remains unchanged; document this observation change before implementation. No blanket overwrite/unlink cleanup, unrelated GET/DELETE redesign, schema changes or universal concurrent-writer claim. Document filesystem/SQLite and privileged/open-writer limits. No real user storage touched. +- **validation**: Tests-first producer183bdafe: nine focused cases/fiveRED plus eight passing API/DB controls. Add focused collision/unsupported/failure controls and run related cases under ASan/UBSan; preserve full logs and exact hashes. Sequential native/source/CMake lease required. +- **status**: Completed (fix `44dbb7ee`) +- **log**: Original rollback/publication RED plus held-directory creation regression corrected; final T7.27/native-779c0c46b674 passes 32/32 under ASan/UBSan. Upload creation uses exclusive openat against the held private directory; no-replace publication and identity-bound rollback preserve competitors. Evidence includes failure/FD cleanup controls and remaining scope limits. +- **files edited/created**: + +## Health-worker partial-start verification (T7.59) + +### T7.59.1: Reproduce partial health-worker startup failure without hanging the runner +- **depends_on**: [T7, T13] +- **agent_type**: worker +- **ownership**: `tests/unit/test_t7_59_start_failure.c`, `tests/unit/cmake/eng/t7_59_start_failure.cmake`, `quality/evidence/T7.59.1.md`; production read-only. +- **description**: Separately investigate system_health_start joining already-created workers while holding lifecycle_lock after a later pthread_create failure. Use actual lifecycle functions/threads with fake collectors and a deterministic handshake proving an earlier worker reached the conflicting lock before injecting creation failure. This is distinct from the array-index scanner finding and must not be treated as its reproduction. +- **acceptance_criteria**: An owned bounded child/test harness records a real failure without leaving threads/processes or hanging the native runner. Only owned child processes may be terminated for timeout containment. Preserve normal startup/shutdown, first-thread failure and cleanup/retry controls; disable real collectors/services. Avoid timing-only sleeps as proof of the interleaving. Retain actual RED and materialize a separate correction if confirmed; no production fix in this producer. +- **validation**: Tests-only actual pthread/lifecycle execution under the exclusive native lease, exact timeout/exit and sanitizer evidence, private resources and explicit cleanup. Production source hashes captured before/after; no scanner dismissal or baseline change. +- **status**: Completed (producer `2bfd7736`; correction pending T7.59.2) +- **log**: Five actual-thread cases: normal and first-failure/retry pass; failures after one, two and three workers each deterministically deadlock, time out and reap only their owned child. Exact lock/fault/join events, full logs and unchanged source hash retained. Production correction separately scoped. +- **files edited/created**: + +### T7.59.2: Prevent partial health-worker startup cleanup deadlocks +- **depends_on**: [T7.59.1, T13] +- **agent_type**: worker +- **ownership**: `src/telemetry/system_health.c` worker startup/lifecycle synchronization only, `tests/unit/test_t7_59_start_failure.c`, `tests/unit/cmake/eng/t7_59_start_failure.cmake`, `quality/evidence/T7.59.2.md`. +- **description**: Correct the deterministic partial pthread_create failure deadlock established by 2bfd7736. Preserve lifecycle ownership while workers are being created and joined; a worker startup gate is an acceptable bounded design. Simply releasing lifecycle_lock around joins without preventing concurrent shutdown/reinitialization is insufficient. +- **acceptance_criteria**: All original failure positions return and clean up with same-runtime retry, normal startup/shutdown and first-failure controls preserved. If a startup gate prevents the original lock-entry interleaving, retain original RED artifacts and adapt the handshake to prove actual workers reached the gate before failure; do not replace real threads with mocked success or weaken timeout failures. Verify concurrent start/shutdown cannot double-join, free an active runtime or leave threads; use deterministic ownership-safe tests with fake collectors only. Preserve original array-bound characterization as a separate pending scanner disposition. +- **validation**: Run focused failure, tier-boundary and relevant existing lifecycle/type tests under ASan/UBSan using the native lease; record exact commands, source hashes, full logs and resource cleanup. No real server/collectors or baseline changes. +- **status**: Completed (fix `b73a1f40`) +- **log**: Per-worker predicate gate preserves lifecycle ownership during creation/failure cleanup. Strengthened9-case producer has5pre-fixRED; final9startup/concurrency+7tier+4types=20/20ASan/UBSan pass in native-a4df2306d621, with originalRED retained. Separate registry/reinit hypothesis tracked in T7.59.3; array warning remains pending review. +- **files edited/created**: + +### T7.59.3: Characterize health shutdown versus reinitialization ownership +- **depends_on**: [T7.59.2, T13] +- **agent_type**: worker +- **ownership**: `tests/unit/test_t7_59_reinit.c`, `tests/unit/cmake/eng/t7_59_reinit.cmake`, `quality/evidence/T7.59.3.md`; production read-only. +- **description**: Investigate a separate pre-existing lifecycle hypothesis: shutdown clears global runtime then releases lifecycle_lock before joining workers and destroying the global provider registry. A concurrent init may publish a new runtime/registry before old shutdown destroys it. Trace registry ownership and supported lifecycle contracts before asserting a defect. +- **acceptance_criteria**: Deterministic owned real-thread tests with fake collectors/providers only; prove relevant ordering with handshakes and bounded timeout containment. Verify new-runtime registry survival and ordinary shutdown/init controls. Do not treat host hardware collectors, arbitrary private corruption or timing-only sleeps as proof. Preserve any actual RED and materialize a separate correction before production edits; no scanner finding closure inferred. +- **validation**: Exclusive native lease, ASan/UBSan, exact source hashes and full logs, owned resources cleaned; no real application services or baseline changes. +- **status**: Not Completed +- **log**: Scoped from T7.59.2 lifecycle review; hypothesis not yet executed or confirmed. +- **files edited/created**: + +## Bulk-tag state reconciliation (T7.172) + +### T7.172.1: Keep successful bulk-tag state consistent while recording data refreshes +- **depends_on**: [T7, T10] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/recordings/TagsOverlay.jsx` bulk state reconciliation only, `web/js/components/preact/RecordingsView.jsx` bulk tag callback/state handling only if required, `web/tests/remediation/t7_172.spec.jsx` original stale-prop regression only, `web/tests/remediation/t7_172_tag_state.spec.jsx`, `quality/evidence/T7.172.1.md`. +- **description**: Resolve the separately reproduced bulk-tag defect: after a successful new tag operation, updating the autocomplete catalog recalculates state from stale recording props and can uncheck the new tag; later same-count refetched rows may not trigger reconciliation. Trace the actual RecordingsView callback, which currently invalidates the query rather than synchronously replacing props. Choose a bounded state reconciliation correction without changing backend APIs or unrelated tag policies. +- **acceptance_criteria**: Preserve the original successful-new-tag checked assertion with realistically delayed recording props. Same-count data refresh and selection changes recompute correct all/some/none states. Existing add/remove payloads, pending/error guards and callbacks remain correct; failed requests do not display successful state. Do not depend solely on a fixture parent that supplies immediate updated records when the real parent does not. The stale-prop regression must become an active passing test before parent T7.172 acceptance, not a permanently opt-in known failure. +- **validation**: Preserve bulk-stale-props-red.log and deterministic failing browser command; add focused delayed/same-count/selection/failure controls, run related tag and accessibility regressions plus types/lint and both asset profiles. Commit names-only work separately from this behavioral fix; restore default assets. No real recordings or remote requests. +- **status**: Completed (fix `9c355497`) +- **log**: Catalog arrival preserves successful local edits; new recording snapshots and selected-ID changes reconcile authoritative state, late requests cannot modify another selection. Original stale-prop, late-catalog and same-size-selection RED become GREEN; 22 focused/143 related pass with failure/pending/callback controls. RecordingsView unchanged. +- **files edited/created**: + +## Independent soak tooling (T30) + +### T30.1: Implement bounded host soak composition and honest resource qualification +- **depends_on**: [T3, T4, T13, T27, T28, T29] +- **agent_type**: worker +- **ownership**: `scripts/quality/soak.py`, `tests/operational/soak/`, `quality/soak-profiles.json`, `quality/evidence/T30.1.md`; existing operational runners and runtime budgets read-only. +- **description**: Implement independent T30 tooling using the completed T5 protocol/inventory evidence e0544701, while its physical calibration remains explicitly deferred. Compose bounded repeated existing media/lifecycle scenarios, archive exact process/tool/source provenance, sample owned NVR and managed-child RSS/CPU/FD/thread data separately from source generators and browser, and evaluate supplied resource/recovery limits. Use existing T13 ownership and serialized runtime contracts; do not nest conflicting locks or adopt existing processes. Request an exact shared-runner extension before modifying files outside this ownership. Assess a separate race-detection build design/compatibility without combining TSan with ASan/UBSan. +- **acceptance_criteria**: Controlled resource growth, missed recovery deadline, child failure, sanitizer output, unexpected media gaps and incomplete/unsupported observations cannot produce a green overall summary. Preserve per-scenario functional results separately from resource qualification. Null physical limits from quality/runtime-budgets.json remain unqualified and make aggregate qualification nonpassing; never substitute made-up limits or silently skip them. Existing T27/T29 tests and tolerances are unchanged. Exercise a bounded actual host composition and record its honest functional/qualification results. +- **validation**: Tests first for profile validation, ownership/sampling identity, aggregation and failure propagation using disposable fixtures; `python3 scripts/quality/soak.py --profile ci --output build/eng-artifacts/T30` after an exclusive native/server/media lease. Tests may supply explicit synthetic thresholds to verify failures, but those thresholds are not physical qualification. Original T30 remains open for T5 physical/resource acceptance. +- **status**: Completed (evidence `04bca5d4`) +- **log**: 34executor tests pass after review RED cases. Six actual host scenarios all functionalPASS; aggregate exit2/unqualified with17null runtime limits, null growth limits and5explicit observation gaps. OriginalT30 remains open for physical/resource acceptance. +- **files edited/created**: + + +## Parallel Execution Groups + +These are dependency frontiers, not permission to exceed three worker slots or +run shared-checkout builds concurrently. Local tasks requiring shared config or +whole-tree evidence get an exclusive integration/validation lease. Expansion +tasks must update this table before their children are scheduled. + +| Wave | Tasks | Can Start When | +| --- | --- | --- | +| 1 | T1, T2, T3, T4 | Immediately; batch into available slots; reserve frontend build lease for T4 | +| 2 | T5, T6, T7, T11; then T8 | Respective inventory/baseline dependencies complete; T7 waits for archived T4 baseline; T8 also waits for T11 | +| 3 | T9 then T9.1–T9.4, T10, T12; eligible T7.* | T8/T11 prerequisites complete; fix tasks honor concrete ownership edges | +| Host soak tooling | T30.1 | T27–T29 complete; T5 protocol available, physical qualification stays deferred/nonpassing | +| 4 | T13, T14, T31 | T9, T6/T10, or T3/T7 respectively; T13 and T14 serialize if shared infra needs overlap | +| 5 | T15; T27, T28, T29; eligible T31.* | Harness/foundation prerequisites complete; operational writers have disjoint files | +| 6 | T16, T17, T18; T30, T32, T33 when ready | T15 for pilot; separate operational/fix gates follow their task dependencies | +| 7 | T19, T39; then T38 | T16 for query; T18 for real contract CI; T15/T39 for remote enforcement | +| 8 | T20, T21 | Async primitives, actual C contracts, and typed client complete | +| 9 | T22 | Form and card integrated | +| 10 | T23 | Pilot wiring complete | +| 11 | T24 | UI evidence, T5 resource profiles, and T38 actual pilot enforcement ready | +| 12 | T25 then T25.* in newly recorded waves | Pilot accepted; exact rollout DAG and ownership validated | +| 13 | T26 then T34 | All materialized rollout dependencies complete | +| 14 | T35 | Soak, C improvement, remediation, and strict migration closure complete | +| 15 | T36 | Final CI integrated and actual target resources available | +| 16 | T37 | All qualification evidence complete | + +## Integration Strategy + +- T15 accepts infrastructure, T22/T24 accept the pilot, T26 accepts rollout, + T32 accepts C changes, and T35 accepts the combined system. Review both file + diffs and generated artifacts at each stable revision. +- Under shared checkout execution, workers edit directly and report files; + only the coordinator updates this plan and reconciles shared files. If using + isolated worktrees later, integrate one dependency-complete task at a time. + Do not require commits simply to satisfy this plan. +- Give every rename a source/import/test/i18n mapping. Avoid concurrent writes + to `openapi.yaml`, generated types, package locks, CMake, integration setup, + shared test snapshots, and the findings inventory. +- If a task discovers a missing prerequisite, ownership conflict, excessive + scope, incompatible dependency, or real bug, record the blocker and evidence + in its log. The coordinator adds a bounded task/edge or obtains missing input; + independent tasks continue. Never bypass a required gate, broaden ownership, + weaken assertions, or mark a missing hardware/admin result completed. +- Finding fixes overlapping upcoming migrations take priority; incorporate the + fix before the migration takes ownership and recapture affected baseline + behavior intentionally. Do not freeze a known defect into the API contract. + +## Testing Strategy + +- Fast PR checks: strict application/test types, lint and i18n/hook rules, + meaningful unit tests, generation drift, findings policy, and asset budgets. +- Real-server checks: role/redaction/side effects, Streams list/edit, all expanded + API contracts, preserved Live and critical UI flows. Contract tests validate + behavior independently of generated schema; snapshots alone are insufficient. +- Native checks: the existing supported CMake profiles, ASan/UBSan, focused + fault paths, real SQLite persistence/retention, media metadata/playability, + and shutdown deadlines. Separate race instrumentation if feasible. +- Scheduled/release checks: real media recovery, repeated lifecycle/soak, + prior-release upgrades, optional production feature builds, and physical + constrained-target qualification. Establish duration/tolerances before results. +- Negative gate demonstrations are temporary fixtures or meaningful policy-tool + tests. Do not commit deliberately broken application code or tests that merely + duplicate a config line. Report flakes, retries, and quarantine honestly. + +## Risks and Mitigations + +- **Risk**: An adaptive plan completes before its discovered work is performed. + **Mitigation**: Mandatory graph expansion and explicit terminal dependencies + fence T26/T32/T33; T37 checks the complete PRD evidence matrix. +- **Risk**: TSX makes unchanged tests disappear or stops i18n enforcement. + **Mitigation**: T2 inventories source-path consumers; T8/T10 handle discovery, + AST semantics and baseline keys before conversions; T34 verifies closure. +- **Risk**: Shared test ports, scratch storage, or PIDs cause destructive races. + **Mitigation**: T13 proves isolation or enforces serialization; unique build + directories and validation leases apply even to otherwise disjoint tasks. +- **Risk**: Strict typing forces the whole large Streams page into the pilot. + **Mitigation**: T22 extracts a typed list/edit feature with explicit adapters; + T25 assigns remaining create/delete/ONVIF work as separate bounded tasks. +- **Risk**: OpenAPI types create false confidence about runtime input or secrecy. + **Mitigation**: Unknown input plus bounded validators; independent redaction, + permission and side-effect assertions against C; explicit dialect support. +- **Risk**: Babel 8/Jest/ESLint additions or generated validators inflate scope. + **Mitigation**: Pin compatible tools in one task, preserve current majors and + Preact, measure output, and avoid a test-runner/framework replacement. +- **Risk**: Scanner reports or supported targets are unavailable. + **Mitigation**: Record missing access/hardware; keep affected acceptance open + while independent development proceeds. Do not substitute a green lean build. + +## Primary Documentation Checked + +These sources inform toolchain choices; resolve exact compatible versions during +T8 and retain the locked choices. Preact v10 documentation matches the current +runtime even though a newer major exists. + +- [Preact TypeScript](https://preactjs.com/guide/v10/typescript/): built-in types + and JSX configuration. +- [Vite TypeScript](https://vite.dev/guide/features.html#typescript): transpilation + is separate from type checking. +- [Babel TypeScript preset](https://babeljs.io/docs/babel-preset-typescript) and + [Jest TypeScript](https://jestjs.io/docs/getting-started#using-typescript): retain + Babel-based execution while checking types independently. +- [typescript-eslint](https://typescript-eslint.io/getting-started/): flat-config + integration; verify selected package peer compatibility. +- [openapi-typescript](https://openapi-ts.dev/introduction): OpenAPI 3.1 support + and runtime-free generated types. +- [Ajv JSON Schema](https://ajv.js.org/json-schema.html) and + [standalone validators](https://ajv.js.org/standalone.html): verify the required + dialect and generate validators at build time. + +## Reviewer Notes + +- Initial local review identified shared test ports/storage, extension-dependent + tests/i18n baselines, the large mixed-purpose Streams page, unknown findings, + and absent physical/admin evidence. The plan assigns explicit owners and gates + rather than assuming these risks away. +- Independent reviewer `review_foundation_plan` reviewed the complete draft and + PRD without editing files or running builds. Applied its dependency fixes: + T8 produces test commands and generation dependencies before consumers; + local T25.* checkpoints produce generated artifacts before rollout workers; + T32 explicitly waits for resource/operational evidence; and T3/T4 archive + immutable baseline artifacts for T5. +- Added T39 to activate real C contract CI before pilot acceptance and T38 to + verify remote required checks separately from local foundation work. T35 + rechecks the final ruleset after later statuses are added. T10 resolves i18n + extension identity before workers rename files. T9's optional expansion now + updates every consumer, and its validation avoids starting load. +- Execution resumed on 2026-10-31 after the previous executor hit its usage limit. + Task entries and their evidence are authoritative; incomplete acceptance gates + remain open. User explicitly deferred physical-device measurements. + +## Migration boundary corrections + +### T7.23.1: Respect quoted SQL identifiers and comments at statement boundaries +- **depends_on**: [T7, T13] +- **agent_type**: local +- **ownership**: `src/database/sqlite_migrate.c` statement boundary parsing only, `tests/unit/test_t7_23.c` statement-boundary cases only, `quality/evidence/T7.23.1.md`. +- **description**: Correct the three statement-boundary failures reproduced by 87e55150. Delimiters inside quoted identifiers/comments must not split valid statements or hide a subsequent disallowed statement from whole-input validation. +- **acceptance_criteria**: Original UP/DOWN rejection and valid-identifier failures become green before any effects. Preserve existing migration allowlist, transactions, triggers and nested CASE handling. Add quote escaping, bracket/backtick identifier and comment controls using private disposable databases only; no policy broadening or remote input claims. The separate tracking-table failure may remain RED until T7.23.2. +- **validation**: Tests first with exact original artifacts retained; focused and existing SQLite migration regressions under ASan/UBSan, full logs and input hashes. No server or real database use. +- **status**: Completed (fix `23e65c8b`) +- **log**: Quoted identifiers, escaped delimiters and comments are tracked correctly; original UP/DOWN rejection plus valid-name/comment/trigger controls pass. Intermediate13cases/1remaining tracking failure versus8priorfailures, seven related controls pass. Tracking cause separately corrected in .2. +- **files edited/created**: + +### T7.23.2: Treat configured migration tracking names as SQL identifiers +- **depends_on**: [T7.23.1, T13] +- **agent_type**: local +- **ownership**: `src/database/sqlite_migrate.c` four tracking-table SQL constructors only, `tests/unit/test_t7_23.c` tracking-name cases only, `quality/evidence/T7.23.2.md`. +- **description**: Correct the independent tracking-name sentinel failure from 87e55150. Quote/escape the configured name consistently as one identifier in create/check/insert/delete operations; preserve version binding. Production callers currently use constant schema_migrations; do not claim remote reachability. +- **acceptance_criteria**: Sentinel remains unchanged for every name; legitimate names including embedded quotes/semicolons and long names round-trip through apply/reload/rollback without truncation. Handle allocation failure consistently and release SQL buffers. All original producer cases pass together after .1. +- **validation**: Tests first, private SQLite fixtures, existing migration regressions and sanitizers; retain actual RED and exact hashes/logs. No scanner dismissal or baseline change. +- **status**: Completed (fix `9f65a26c`) +- **log**: All four SQL constructors use quoted SQLite identifiers and dynamic buffers. Original sentinel and new quoted/1024-character roundtrip controls pass; final31/31ASan/UBSan. Source review covers allocation returns; no injected allocation-failure or remote-reachability claim. +- **files edited/created**: + +### T7.23.3: Verify trigger terminators independently of identifier keywords +- **depends_on**: [T7.23.2, T13] +- **agent_type**: local +- **ownership**: `src/database/sqlite_migrate.c` statement boundary/word token helpers only, `tests/unit/test_t7_23.c` trigger boundary cases only, `quality/evidence/T7.23.3.md`. +- **description**: Follow-up review found SQLite accepts unquoted trigger names BEGIN and BEGIN-prefixed dollar/Unicode identifiers. Counting BEGIN/CASE tokens before the trigger body may incorrectly swallow subsequent statements. Reproduce through real public migration APIs and correct the boundary mechanism if confirmed. +- **acceptance_criteria**: Legitimate trigger names/body expressions still work; following statements remain individually validated before effects. Preserve original quoted/comment/string controls, nested CASE, comments around terminators, and linear lexical traversal rather than repeatedly scanning the whole input for each semicolon. No allowlist expansion or universal SQL-safety claim. +- **validation**: Tests first against current source, private in-memory SQLite/files only, sanitizer focused and existing migration/init regressions, exact artifacts and source hashes. Parent stays open until outcome is verified. +- **status**: Completed (fix `c98876b5`) +- **log**: Three actual trigger-boundary RED cases corrected by semicolon/END/semicolon token recognition; keyword/dollar/Unicode names no longer hide following statements. Constant-state linear walker, unchanged allowlist, final37/37ASan/UBSan. +- **files edited/created**: + +### T7.168.1: Reproduce Zone Editor first-toggle state loss +- **depends_on**: [T7, T10, T7.162] +- **agent_type**: worker +- **ownership**: `web/tests/remediation/t7_168.spec.jsx` original unselected-card regression only, `web/tests/remediation/t7_168_zone_state.spec.jsx`, `quality/evidence/T7.168.1.md`; production read-only. +- **description**: Separately characterize the ZoneEditor card-selection/checkbox ordering issue exposed by T7.168. A first checkbox activation in an unselected card may trigger card selection before the change event and restore the prior checked state. Verify actual pointer and keyboard-first flows, independent of programmatic fill shortcuts. +- **acceptance_criteria**: Keep the original unselected-card assertion active and retain the passing preselected-card control. Confirm or reject the defect using actual browser events, state/payload evidence and real component source. Preserve zone geometry/name/color and actual callbacks. No source correction until separately materialized; parent accessibility acceptance stays open if its original behavioral assertion remains failing. +- **validation**: Focused real-browser fixtures without a backend or operator state; tests-only after frontend lease handoff; exact RED/controls/logs. No fabricated success by preselecting the failing workflow. +- **status**: Completed (producer `be3ba449`, fixture correction `7262eaba`) +- **log**: Actual pointer-first and keyboard-first flows fail before correction, with four label/preselected controls passing. Original unselected-card assertion remains active. Final tracked RED and exact callback/save payload traces preserved; source unchanged until separately committed .2 correction. +- **files edited/created**: + +### T7.168.2: Preserve first Zone Editor checkbox activation across card selection +- **depends_on**: [T7.168.1, T7.162] +- **agent_type**: worker +- **ownership**: `web/js/components/preact/ZoneEditor.jsx` checkbox/card event ordering only, `web/tests/remediation/t7_168.spec.jsx` original unselected/preselected controls only, `web/tests/remediation/t7_168_zone_state.spec.jsx`, `web/tests/remediation/t7_162.spec.jsx` Node Zone Editor event-order fixture compatibility only, `quality/evidence/T7.168.2.md`. +- **description**: Correct the confirmed first-toggle state loss: ancestor card selection rerenders the controlled checkbox between native click and change, restoring the prior checked value. Both real pointer-first and Tab/Space-first workflows reproduce without programmatic fill. Keep source changes bounded to event/state ordering. +- **acceptance_criteria**: Original unselected-card assertion and dedicated pointer/keyboard cases pass actively. A first native toggle updates enabled state once and saves the intended value while selecting the correct card. Preselected toggles and subsequent toggles remain correct; geometry/name/color and field editing unchanged. Preserve natural checkbox behavior, focus and card-selection callbacks; do not mask failure by preselecting fixtures or suppressing useful callbacks. +- **validation**: Retain actual2RED/2control producer logs, source hashes and payload traces; focused/related browser tests, both types, full lint and modern/default asset profiles with default restored. Production edits only after explicit frontend lease handoff. +- **status**: Completed (fix `881ee46d`) +- **log**: Checkbox change records native enabled state before card selection; clicks still bubble. Original regression plus six pointer/keyboard/label/repeat/focus controls pass. Final 38 focused/83 related cases, types, unchanged lint and 43 modern/66 default asset budgets pass; default restored. +- **files edited/created**: diff --git a/include/core/shutdown_coordinator.h b/include/core/shutdown_coordinator.h index 56afe9f9f..1e6b45c00 100644 --- a/include/core/shutdown_coordinator.h +++ b/include/core/shutdown_coordinator.h @@ -77,7 +77,9 @@ bool is_shutdown_initiated(void); bool is_coordinator_destroyed(void); // Wait for all components to stop (with timeout) -// Returns true if all components stopped, false if timeout +// While active, returns true only after all components report stopped. Timeout/wait failure +// returns false without changing live component states; spurious wakes retain +// the original absolute deadline. bool wait_for_all_components_stopped(int timeout_seconds); // Get the global shutdown coordinator instance diff --git a/include/database/db_embedded_migrations.h b/include/database/db_embedded_migrations.h index 58fd0f8db..1c2e46a15 100644 --- a/include/database/db_embedded_migrations.h +++ b/include/database/db_embedded_migrations.h @@ -2091,6 +2091,32 @@ static const char migration_0089_down[] = " AND v.revision=r.storage_policy_version);\n" "DROP INDEX idx_recordings_storage_policy_revision;"; +static const char migration_0090_up[] = + "ALTER TABLE storage_deletion_objects ADD COLUMN stale_guard_kind TEXT\n" + " CHECK (stale_guard_kind IS NULL OR stale_guard_kind IN ('missing', 'empty'));\n" + "ALTER TABLE storage_deletion_objects ADD COLUMN stale_guard_dev INTEGER;\n" + "ALTER TABLE storage_deletion_objects ADD COLUMN stale_guard_ino INTEGER\n" + " CHECK (CASE\n" + " WHEN stale_guard_kind IS NULL THEN stale_guard_dev IS NULL AND stale_guard_ino IS NULL\n" + " WHEN stale_guard_kind = 'missing' THEN stale_guard_dev IS NULL AND stale_guard_ino IS NULL\n" + " WHEN stale_guard_kind = 'empty' THEN stale_guard_dev IS NOT NULL AND stale_guard_dev >= 0\n" + " AND stale_guard_ino IS NOT NULL AND stale_guard_ino > 0\n" + " ELSE 0\n" + " END);"; + +static const char migration_0090_down[] = + "-- Removing these guards can make a pending operation delete replacement footage.\n" + "-- Downgrade is unsupported even when no guarded work currently appears pending;\n" + "-- do not run an older binary against this database. Restore a separately verified\n" + "-- pre-upgrade backup under a separately qualified recovery procedure instead.\n" + "CREATE TEMP TABLE IF NOT EXISTS stale_guard_downgrade_blocker (value INTEGER);\n" + "CREATE TEMP TRIGGER IF NOT EXISTS prevent_stale_guard_downgrade\n" + "BEFORE INSERT ON stale_guard_downgrade_blocker\n" + "BEGIN\n" + " SELECT RAISE(ABORT, 'stale deletion guards cannot be downgraded');\n" + "END;\n" + "INSERT INTO stale_guard_downgrade_blocker VALUES (1);"; + static const migration_t embedded_migrations_data[] = { { .version = "0001", @@ -2715,8 +2741,15 @@ static const migration_t embedded_migrations_data[] = { .sql_down = migration_0089_down, .is_embedded = true }, + { + .version = "0090", + .description = "add_stale_deletion_guards", + .sql_up = migration_0090_up, + .sql_down = migration_0090_down, + .is_embedded = true + }, }; -#define EMBEDDED_MIGRATIONS_COUNT 89 +#define EMBEDDED_MIGRATIONS_COUNT 90 #endif /* DB_EMBEDDED_MIGRATIONS_H */ diff --git a/include/database/db_recordings.h b/include/database/db_recordings.h index 220221e02..dd8d439ce 100644 --- a/include/database/db_recordings.h +++ b/include/database/db_recordings.h @@ -395,6 +395,14 @@ int get_recordings_for_pressure_cleanup_default_target( int get_stale_incomplete_recordings(recording_metadata_t *recordings, int max_count, int older_than_seconds); +/* Bounded ID-keyset scan: first page (after_id=0) snapshots maximum_id. + * Keep cutoff/maximum_id fixed and pass the last returned ID on continuation; + * retained rows and concurrent inserts cannot cause revisits or extend the scan. + * Pending deletions are excluded. Returns count, or -1 on a database error. */ +int get_stale_incomplete_recordings_page(recording_metadata_t *recordings, int max_count, + time_t cutoff, uint64_t after_id, uint64_t *maximum_id); + + /** * Get total storage bytes used by a stream from the database * diff --git a/include/storage/storage_deletion.h b/include/storage/storage_deletion.h index 5a6d70c99..3e797c515 100644 --- a/include/storage/storage_deletion.h +++ b/include/storage/storage_deletion.h @@ -8,11 +8,22 @@ #ifndef LIGHTNVR_STORAGE_DELETION_H #define LIGHTNVR_STORAGE_DELETION_H #include +#include +#include "database/db_recordings.h" /* Atomically claim logical deletion and journal every physical copy. Protected * recordings and actively transferring recordings return -2 without mutation. */ int storage_recording_delete(uint64_t recording_id, const char *reason, uint64_t *local_bytes_removed); +/* Internal startup recovery only: selected stale incomplete row on a disabled + * stream, with no live writer and available canonical local storage. Pass the + * observed empty regular-file identity, or NULL after confirmed ENOENT. Exact + * row/identity guards are rechecked and persisted before any physical deletion. + * Attributed filesystem targets require matching target/key/path and active + * mount guards. Unknown/nonlocal storage, copies/derivatives retain the row. */ +int storage_recording_prune_stale(const recording_metadata_t *expected, + time_t cutoff, const struct stat *expected_empty); + /* Recheck the applied policy/override inside the deletion transaction. Returns * -2 if a previously selected recording is no longer eligible for expiry. */ int storage_recording_expire_policy(uint64_t recording_id); diff --git a/include/video/recording_path.h b/include/video/recording_path.h index fef6a6ee2..d586595fc 100644 --- a/include/video/recording_path.h +++ b/include/video/recording_path.h @@ -61,6 +61,23 @@ int prepare_placed_mp4_recording_path( const config_t *config, const char *stream_name, time_t timestamp, char *output, size_t output_size, storage_placement_t *placement); +/** + * Atomically reserve a fresh .mp4 pathname immediately before writing a segment. + * Keeps the timestamped planned path when unused, otherwise tries bounded numeric + * suffixes. On success updates path and placement.object_key together and returns + * an owned open descriptor. On failure neither caller value changes; errno is set. + * The planned-path helpers above do not reserve or create a recording file. + */ +int reserve_mp4_recording_path(char *path, size_t path_size, + storage_placement_t *placement); + +/** + * Close an owned reservation after the segment call. Remove only a still-empty + * regular file whose inode matches the held descriptor; preserve written media, + * replaced paths and other writers' files. Returns -1 on cleanup/close failure. + */ +int release_mp4_recording_path(int descriptor, const char *path); + /** * Build the on-demand HEVC->H.264 playback-cache path for a recording, * mirroring the thumbnails/_.jpg convention so cleanup can key off diff --git a/package-lock.json b/package-lock.json index d36b64490..181c0f13c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,14 +8,63 @@ "name": "lightnvr", "version": "1.0.0", "devDependencies": { + "@apidevtools/json-schema-ref-parser": "16.0.5", "@playwright/test": "^1.62.1", + "@types/node": "24.19.2", + "ajv": "8.20.0", + "ajv-formats": "3.0.1", + "esbuild": "0.28.2", + "openapi-typescript": "7.13.0", "playwright": "^1.62.1", - "sharp": "^0.35.5" + "sharp": "^0.35.5", + "typescript": "5.9.3", + "yaml": "2.9.0" }, "engines": { "node": ">=24.11.0 <25" } }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "16.0.5", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-16.0.5.tgz", + "integrity": "sha512-z3LiEMqTP2FNu6u8bdUgBYErVuwMSZReOJy1MCEIB0BDHMoNgBtkciby32HOlcrfWwrOSzRU8YIsZ0CogYRuYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-yaml": "^5.4.2" + }, + "engines": { + "node": ">=22.19.0" + }, + "peerDependencies": { + "@types/json-schema": "^7.0.15" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@emnapi/runtime": { "version": "1.11.3", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", @@ -23,8 +72,450 @@ "dev": true, "license": "MIT", "optional": true, - "dependencies": { - "tslib": "^2.4.0" + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" } }, "node_modules/@img/colour": { @@ -618,6 +1109,221 @@ "node": ">=20" } }, + "node_modules/@redocly/ajv": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.11.2.tgz", + "integrity": "sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js-replace": "^1.0.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@redocly/config": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@redocly/config/-/config-0.22.0.tgz", + "integrity": "sha512-gAy93Ddo01Z3bHuVdPWfCwzgfaYgMdaZPcfL7JZ7hWJoK9V0lXDbigTWkhiPFAaLWzbOJ+kbUQG1+XwIm0KRGQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@redocly/openapi-core": { + "version": "1.34.22", + "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.34.22.tgz", + "integrity": "sha512-dEyx4uHh6Pc6ZkdU9Xe8rA4h12Odq6QKU/fqseBn07xwVwskhHiF3KHhqmw5BR+jAiFTvKTmwCafFql/k1pSzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@redocly/ajv": "8.11.2", + "@redocly/config": "0.22.0", + "colorette": "1.4.0", + "https-proxy-agent": "7.0.6", + "js-levenshtein": "1.1.6", + "js-yaml": "4.3.2", + "minimatch": "5.1.9", + "pluralize": "8.0.0", + "yaml-ast-parser": "0.0.43" + }, + "engines": { + "node": ">=18.17.0", + "npm": ">=9.5.0" + } + }, + "node_modules/@redocly/openapi-core/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/@redocly/openapi-core/node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/node": { + "version": "24.19.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.19.2.tgz", + "integrity": "sha512-93d49wCWJkCyN87MxLpNN6KXPCwFw7bVUg79mGUV1OS40JGWwVCr8n61DXCSqz7CV6/VfPkb7xec7uP2M+QHWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": ">=7.24.0 <7.24.7" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/argparse": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-3.0.4.tgz", + "integrity": "sha512-fwWK3Zq22l4XJJjx5mcBHGikIk+hJqO+32dPm0Jur7+rtG5x1scOjSfdT9ZKeWHPpsya/l0HHK4tDImBkTrtvA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "PSF-2.0" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/change-case": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz", + "integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -628,6 +1334,72 @@ "node": ">=8" } }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.9.tgz", + "integrity": "sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", @@ -643,6 +1415,146 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/index-to-position": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz", + "integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/js-levenshtein": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-5.5.0.tgz", + "integrity": "sha512-paNr4LFIFPjeeSmwNYdrb8FafLLG/Cr2Af3GjbsHJ5CxIjH4r0c2emlmRdAtpPAieP1gRVzk6buBbVP5gBMGAg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^3.0.3" + }, + "bin": { + "js-yaml": "bin/js-yaml.mjs" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/openapi-typescript": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/openapi-typescript/-/openapi-typescript-7.13.0.tgz", + "integrity": "sha512-EFP392gcqXS7ntPvbhBzbF8TyBA+baIYEm791Hy5YkjDYKTnk/Tn5OQeKm5BIZvJihpp8Zzr4hzx0Irde1LNGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@redocly/openapi-core": "^1.34.6", + "ansi-colors": "^4.1.3", + "change-case": "^5.4.4", + "parse-json": "^8.3.0", + "supports-color": "^10.2.2", + "yargs-parser": "^21.1.1" + }, + "bin": { + "openapi-typescript": "bin/cli.js" + }, + "peerDependencies": { + "typescript": "^5.x" + } + }, + "node_modules/parse-json": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", + "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, "node_modules/playwright": { "version": "1.62.1", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.62.1.tgz", @@ -675,6 +1587,26 @@ "node": ">=20" } }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/semver": { "version": "7.8.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", @@ -738,6 +1670,19 @@ } } }, + "node_modules/supports-color": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -745,6 +1690,80 @@ "dev": true, "license": "0BSD", "optional": true + }, + "node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "dev": true, + "license": "MIT" + }, + "node_modules/uri-js-replace": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uri-js-replace/-/uri-js-replace-1.0.2.tgz", + "integrity": "sha512-VeBBt/nMxedczht3KDL2yBBMJqIxpCQLMZp3UJ1otcWwUHdQW4IiyHpJivExzSK+FdxTowRfGL4FmwDqH9+S8Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yaml-ast-parser": { + "version": "0.0.43", + "resolved": "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz", + "integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } } } } diff --git a/package.json b/package.json index 8293123f4..74d7b1908 100644 --- a/package.json +++ b/package.json @@ -6,19 +6,28 @@ "node": ">=24.11.0 <25" }, "scripts": { - "test": "npx playwright test", - "test:api": "npx playwright test --grep @api", - "test:ui": "npx playwright test --grep @ui", - "test:go2rtc": "npx playwright test --grep @go2rtc", + "test": "node scripts/quality/run-integration.mjs --task npm-test --project all", + "test:api": "node scripts/quality/run-integration.mjs --task npm-api --project api --grep @api", + "test:ui": "node scripts/quality/run-integration.mjs --task npm-ui --project ui --grep @ui", + "test:go2rtc": "node scripts/quality/run-integration.mjs --task npm-go2rtc --project api --grep @go2rtc", "test:report": "npx playwright show-report", "test:load": "node --experimental-strip-types tests/load/index.ts", "test:load:api": "node --experimental-strip-types tests/load/index.ts --tags api", "test:load:html": "node --experimental-strip-types tests/load/index.ts --tags html", - "test:load:heavy": "node --experimental-strip-types tests/load/index.ts -n 1000 -c 50" + "test:load:heavy": "node --experimental-strip-types tests/load/index.ts -n 1000 -c 50", + "typecheck:tests": "tsc --project tests/tsconfig.json" }, "devDependencies": { + "@apidevtools/json-schema-ref-parser": "16.0.5", "@playwright/test": "^1.62.1", + "@types/node": "24.19.2", + "ajv": "8.20.0", + "ajv-formats": "3.0.1", + "esbuild": "0.28.2", + "openapi-typescript": "7.13.0", "playwright": "^1.62.1", - "sharp": "^0.35.5" + "sharp": "^0.35.5", + "typescript": "5.9.3", + "yaml": "2.9.0" } } diff --git a/patches/faac/0001-bound-input-pointer-arithmetic.patch b/patches/faac/0001-bound-input-pointer-arithmetic.patch new file mode 100644 index 000000000..6eeee27ab --- /dev/null +++ b/patches/faac/0001-bound-input-pointer-arithmetic.patch @@ -0,0 +1,78 @@ +--- a/libfaac/frame.c ++++ b/libfaac/frame.c +@@ -540,7 +540,7 @@ + * de-interleaving and converting to float once here so the rest of the + * encoder is agnostic to the input format. samplesInput may be any count that + * fits the FIFO; returns -1 on overflow or an invalid format. */ +-static int appendInputFifo(faacEncStruct *hEncoder, int32_t *inputBuffer, ++static int appendInputFifo(faacEncStruct *hEncoder, const void *inputBuffer, + unsigned int samplesInput) + { + unsigned int numChannels = hEncoder->numChannels; +@@ -554,8 +554,9 @@ + float *dst = hEncoder->inputFifo[channel] + hEncoder->inputFifoFill; + switch (hEncoder->config.inputFormat) { + case INPUT_16BIT: { +- short *src = (short *)inputBuffer + hEncoder->config.channel_map[channel]; +- for (i = 0; i < spch; i++) { dst[i] = (float)*src; src += numChannels; } ++ const int16_t *src = (const int16_t *)inputBuffer; ++ for (i = 0; i < spch; i++) ++ dst[i] = (float)src[i * numChannels + hEncoder->config.channel_map[channel]]; + break; + } + case INPUT_24BIT: { +@@ -573,13 +574,15 @@ + break; + } + case INPUT_32BIT: { +- int32_t *src = (int32_t *)inputBuffer + hEncoder->config.channel_map[channel]; +- for (i = 0; i < spch; i++) { dst[i] = (1.0f/256) * (float)*src; src += numChannels; } ++ const int32_t *src = (const int32_t *)inputBuffer; ++ for (i = 0; i < spch; i++) ++ dst[i] = (1.0f/256) * (float)src[i * numChannels + hEncoder->config.channel_map[channel]]; + break; + } + case INPUT_FLOAT: { +- float *src = (float *)inputBuffer + hEncoder->config.channel_map[channel]; +- for (i = 0; i < spch; i++) { dst[i] = (float)*src; src += numChannels; } ++ const float *src = (const float *)inputBuffer; ++ for (i = 0; i < spch; i++) ++ dst[i] = (float)src[i * numChannels + hEncoder->config.channel_map[channel]]; + /* Not audio: silence NaN, Inf and anything past the limit. A + * separate pass because the conditional store keeps the + * compiler from unrolling it. */ +@@ -720,7 +723,7 @@ + #define TNS_ATTACK_MIN 0.5f + + int faacEncEncode(faacEncHandle hpEncoder, +- int32_t *inputBuffer, ++ const void *inputBuffer, + unsigned int samplesInput, + unsigned char *outputBuffer, + unsigned int bufferSize +--- a/libfaac/faac.c ++++ b/libfaac/faac.c +@@ -392,10 +392,8 @@ + if (in_samples > faacFrameSamples(h) * h->numChannels) + return FAAC_ERR_INPUT_OVERFLOW; + +- /* faacEncEncode reinterprets the input bytes per the configured +- * input_format; the int32_t* parameter is historical and does not imply an +- * int32 layout. */ +- rc = faacEncEncode((faacEncHandle)h, (int32_t *)(uintptr_t)in, ++ /* The private core selects the typed view only after input_format. */ ++ rc = faacEncEncode((faacEncHandle)h, in, + in_samples, out, out_cap); + /* A too-small output buffer was already rejected above, so a negative + * return here is an unexpected core fault, not a buffer-size problem -- +--- a/libfaac/faac_internal.h ++++ b/libfaac/faac_internal.h +@@ -79,7 +79,7 @@ + unsigned long *pSizeOfDecoderSpecificInfo); + faacEncHandle faacEncOpen(unsigned long sampleRate, unsigned int numChannels, + unsigned long *inputSamples, unsigned long *maxOutputBytes); +-int faacEncEncode(faacEncHandle hEncoder, int32_t *inputBuffer, unsigned int samplesInput, ++int faacEncEncode(faacEncHandle hEncoder, const void *inputBuffer, unsigned int samplesInput, + unsigned char *outputBuffer, unsigned int bufferSize); + int faacEncClose(faacEncHandle hEncoder); + diff --git a/patches/faac/manifest.cmake b/patches/faac/manifest.cmake new file mode 100644 index 000000000..8e8ac69cf --- /dev/null +++ b/patches/faac/manifest.cmake @@ -0,0 +1,127 @@ +set(FAAC_MANIFEST_SHA256 "d0b1cd1ac9cadbcdab1e6743117fa8de20336608597872ba3b4e6090f9186f1f") +set(FAAC_UPSTREAM_COMMIT "eb4928a76fd075f9bab20e8195df84f937da289b") +set(FAAC_PATCH_SHA256 "b1284e6d79a6126f7efd37c19024bb47d4b9b9bf14fb791da7b5a9e68185b5ce") +set(FAAC_INPUT_RECORDS + "COPYING|20e50fe7aae3e56378ebf0417d9de904f55a0e61e4df315333e632a4d3555d95" + "include/faac.h|e97ac5bc5c552c4f85df9a1807cc61a40cc56f9666aeb5f0f8c8958c4cf421e7" + "include/meson.build|d3b6b545696ca89e653097a139d73231ab8c74ee6e8b6743227dd90fc962fff4" + "libfaac/atomic.h|de2a1ea4e24c980c389ca1892502da79f4d245a01dbf944fffedcbc70464e43c" + "libfaac/bitstream.c|0d8ec3510be0bade46fc39162cef81c8f30f3d46fa120ff340aa556de5a95d5e" + "libfaac/bitstream.h|a3b70fffd0cd1bae2ef929888f9bf22928104ffb38d67740d859ba327b5c6904" + "libfaac/blockswitch.c|7dc8e96a20b323cf5abdf3457a9da031b854f3ce7d9c42c4b7d8149fd5fa97b3" + "libfaac/blockswitch.h|3981b58b9a70ad4994e4f7f852d5a48f94c5adfd974f475aa521d7d4f76cd80c" + "libfaac/channels.c|db45f0b2c5e93ccba0233e36b8ab70477ea040d9b5b01462e6779a02794a8b86" + "libfaac/channels.h|16087966954a71dfcfd75d9fa5bfc878ed62a5669f9a5e8bc283d7f2a07b3eeb" + "libfaac/coder.h|2f77cf3be9654c3d358859dac08c4289145f293484621ae53bc9abf0aed6fc03" + "libfaac/cpu_compute.c|8474a2e3c7a2a4e1ead63838d4e7210fce8366625bad737f295264c357fec630" + "libfaac/cpu_compute.h|4137558c11e0a6bd695a1321f772ae848f927ba557bfcd225ae55834cb21034f" + "libfaac/faac.c|29ca7a51f3cb06b00fc70e31ae5cad8cd6ef2828f14bdfb730a5b5431d5c68f6" + "libfaac/faac_internal.h|f7c8eef5d06359e2bbd8fbea7828f76f829bbd6efe46e46992a7a3a3dbc35640" + "libfaac/fft.c|90d109b6d4ead35f5c4eaba58afcffe246e2e38e7bfbe0b6106a29ff2abca1d1" + "libfaac/fft.h|dff5196cff10275e71003385e947685b4bb5aa350fdb72371b3b683608e4e97b" + "libfaac/filtbank.c|7248f5262003b302162114bfe05c433722abebc68080916f8371162b022eea34" + "libfaac/filtbank.h|19e06c5eeb290e5daa7729125c7273bef9585d90a2674b48b7dd3f608b99fb3c" + "libfaac/frame.c|174e7948bd59876b6be06913aaa0e340332173a2d408be45eee317c8009a0617" + "libfaac/frame.h|1166b8ad39bf250cc215d6dde833121e9141f7b0219b6bb006e21c83f92404c9" + "libfaac/huff2.c|4209e8e24fed7802ac7da61a08b17d80e005480277e848a103400f80b222e4e0" + "libfaac/huff2.h|91491423c9843fa592bc42d64990cbdd1a98977fe498002524e177e4376ad17e" + "libfaac/huffdata.c|1fa103248dcb7b01fbd1bc1469e4e6237a876529781b129b9cc09b191d3b69df" + "libfaac/huffdata.h|7fd091d65e9d014c1b24d5dcaec88034e6010fb518bb933d7a383ad36ecc7f13" + "libfaac/libfaac.def|1b79287f19960f816b18dcfc4671ffd0c1cccfaece22863c4124c9ce96b8d08c" + "libfaac/meson.build|6aba38855395bce555a23086d642faedf2c3f503f164e43e8a935389396206c9" + "libfaac/quantize.c|b490d95d4be952ea438752461e8e8bce9116c4375a90e575967afbe8e2f67443" + "libfaac/quantize.h|6f9b624b9787866e87e48d77cbcc648c0e882b4e1831786faa617e9d4269d549" + "libfaac/quantize_sse.c|39674732d1d98f6d92c85c58b3346cb835354bb5e5e477187ba1eb9db366820e" + "libfaac/ratecontrol.c|64c80c165f0a2df845fc20dbf227408aea959f61907c44d7fd555da561cabbc7" + "libfaac/ratecontrol.h|d7377ac08436f2285b24355eeceb83c2aa863fd2386bb2d9fe7b7040b4bd340d" + "libfaac/resample.c|0004ce310b5c7bc320eb193cc85603c37aee085aef8456bca7593ac57c0a1b7e" + "libfaac/resample.h|ef8382d80c4aa59909babba8b206ea63d585ee961f068f7c472241f25a56230a" + "libfaac/sbr.c|bedaf0823ec78c7d0262246b89598b53ac45d22e2eaf2c033d68efc1b1afefeb" + "libfaac/sbr.h|ace862bd8c63e225bd1d270f80daf0ca6ac2651c250cd9092d0ebc65907be20d" + "libfaac/sbr_analysis.c|60641129ce70f014b577cba00cf9e05be50ca2cea763e35e9ca05fb262d8da38" + "libfaac/sbr_analysis.h|73af1a1f8d045919a39b12c1156f0c07345864eac81aaa012c9e9cf378db8074" + "libfaac/sbr_bitstream.c|c4c459bd28650820e56c105d0ffc92d4c2210dfd350bbda97802d711a3a5edd8" + "libfaac/sbr_internal.h|0cdccb874908db29cb3b88e7b756e451a101399392289554241190defb2567da" + "libfaac/sbr_tables.c|a698409114fbe2da36ede447422a985e91a0e7e53482ea8424949b0ba410e3e6" + "libfaac/sbr_tables.h|262750f2ccd5058f135aae6fc36a856005fd530949d3a05bb5e4a1aa9cd9f7d0" + "libfaac/stats.h|1f5f6bf33735f861bca9f090b40282bfafeebecf5dc672e5640375a09361e32a" + "libfaac/stereo.c|2f5168bd1d95a4774514f46d1b9b32b09aab53479995ce2935c6e9bc83fe8e5e" + "libfaac/stereo.h|a893132aef72367b0b188e39e297b72a904f5b8c5d9082f9de2db920419ad1c3" + "libfaac/tns.c|9be4c7b00cf42f590e3d819c32429146a399fdafb65ba6ba52f9c79aa3571446" + "libfaac/tns.h|4d1c48722af6c5e9a2062330b3f54285236df76c50cf817a9a9cb9e4bca6f51e" + "libfaac/util.c|471d2fd0abcb09080e6a27692294bd6587b58961c248133bedda1505f9462d83" + "libfaac/util.h|c951a13f74684f2556e9afcad5c44adf2986b41a218e870393b3cb7e49392d0a" +) +set(FAAC_OUTPUT_RECORDS + "COPYING|20e50fe7aae3e56378ebf0417d9de904f55a0e61e4df315333e632a4d3555d95" + "include/faac.h|e97ac5bc5c552c4f85df9a1807cc61a40cc56f9666aeb5f0f8c8958c4cf421e7" + "include/meson.build|d3b6b545696ca89e653097a139d73231ab8c74ee6e8b6743227dd90fc962fff4" + "libfaac/atomic.h|de2a1ea4e24c980c389ca1892502da79f4d245a01dbf944fffedcbc70464e43c" + "libfaac/bitstream.c|0d8ec3510be0bade46fc39162cef81c8f30f3d46fa120ff340aa556de5a95d5e" + "libfaac/bitstream.h|a3b70fffd0cd1bae2ef929888f9bf22928104ffb38d67740d859ba327b5c6904" + "libfaac/blockswitch.c|7dc8e96a20b323cf5abdf3457a9da031b854f3ce7d9c42c4b7d8149fd5fa97b3" + "libfaac/blockswitch.h|3981b58b9a70ad4994e4f7f852d5a48f94c5adfd974f475aa521d7d4f76cd80c" + "libfaac/channels.c|db45f0b2c5e93ccba0233e36b8ab70477ea040d9b5b01462e6779a02794a8b86" + "libfaac/channels.h|16087966954a71dfcfd75d9fa5bfc878ed62a5669f9a5e8bc283d7f2a07b3eeb" + "libfaac/coder.h|2f77cf3be9654c3d358859dac08c4289145f293484621ae53bc9abf0aed6fc03" + "libfaac/cpu_compute.c|8474a2e3c7a2a4e1ead63838d4e7210fce8366625bad737f295264c357fec630" + "libfaac/cpu_compute.h|4137558c11e0a6bd695a1321f772ae848f927ba557bfcd225ae55834cb21034f" + "libfaac/faac.c|e08d5f6710d0f7442edb4ec7953e9a596300ec566a1b173ec5ef9df0d66882fd" + "libfaac/faac_internal.h|8a220de11ffde45f74fa18f5c8bab757ad0052671f4e20745433cd98935af6b1" + "libfaac/fft.c|90d109b6d4ead35f5c4eaba58afcffe246e2e38e7bfbe0b6106a29ff2abca1d1" + "libfaac/fft.h|dff5196cff10275e71003385e947685b4bb5aa350fdb72371b3b683608e4e97b" + "libfaac/filtbank.c|7248f5262003b302162114bfe05c433722abebc68080916f8371162b022eea34" + "libfaac/filtbank.h|19e06c5eeb290e5daa7729125c7273bef9585d90a2674b48b7dd3f608b99fb3c" + "libfaac/frame.c|a1d01bd81c58f56b71fbfabdbb18ba7bc7326f2ed859236ec8a4ca629031a0bd" + "libfaac/frame.h|1166b8ad39bf250cc215d6dde833121e9141f7b0219b6bb006e21c83f92404c9" + "libfaac/huff2.c|4209e8e24fed7802ac7da61a08b17d80e005480277e848a103400f80b222e4e0" + "libfaac/huff2.h|91491423c9843fa592bc42d64990cbdd1a98977fe498002524e177e4376ad17e" + "libfaac/huffdata.c|1fa103248dcb7b01fbd1bc1469e4e6237a876529781b129b9cc09b191d3b69df" + "libfaac/huffdata.h|7fd091d65e9d014c1b24d5dcaec88034e6010fb518bb933d7a383ad36ecc7f13" + "libfaac/libfaac.def|1b79287f19960f816b18dcfc4671ffd0c1cccfaece22863c4124c9ce96b8d08c" + "libfaac/meson.build|6aba38855395bce555a23086d642faedf2c3f503f164e43e8a935389396206c9" + "libfaac/quantize.c|b490d95d4be952ea438752461e8e8bce9116c4375a90e575967afbe8e2f67443" + "libfaac/quantize.h|6f9b624b9787866e87e48d77cbcc648c0e882b4e1831786faa617e9d4269d549" + "libfaac/quantize_sse.c|39674732d1d98f6d92c85c58b3346cb835354bb5e5e477187ba1eb9db366820e" + "libfaac/ratecontrol.c|64c80c165f0a2df845fc20dbf227408aea959f61907c44d7fd555da561cabbc7" + "libfaac/ratecontrol.h|d7377ac08436f2285b24355eeceb83c2aa863fd2386bb2d9fe7b7040b4bd340d" + "libfaac/resample.c|0004ce310b5c7bc320eb193cc85603c37aee085aef8456bca7593ac57c0a1b7e" + "libfaac/resample.h|ef8382d80c4aa59909babba8b206ea63d585ee961f068f7c472241f25a56230a" + "libfaac/sbr.c|bedaf0823ec78c7d0262246b89598b53ac45d22e2eaf2c033d68efc1b1afefeb" + "libfaac/sbr.h|ace862bd8c63e225bd1d270f80daf0ca6ac2651c250cd9092d0ebc65907be20d" + "libfaac/sbr_analysis.c|60641129ce70f014b577cba00cf9e05be50ca2cea763e35e9ca05fb262d8da38" + "libfaac/sbr_analysis.h|73af1a1f8d045919a39b12c1156f0c07345864eac81aaa012c9e9cf378db8074" + "libfaac/sbr_bitstream.c|c4c459bd28650820e56c105d0ffc92d4c2210dfd350bbda97802d711a3a5edd8" + "libfaac/sbr_internal.h|0cdccb874908db29cb3b88e7b756e451a101399392289554241190defb2567da" + "libfaac/sbr_tables.c|a698409114fbe2da36ede447422a985e91a0e7e53482ea8424949b0ba410e3e6" + "libfaac/sbr_tables.h|262750f2ccd5058f135aae6fc36a856005fd530949d3a05bb5e4a1aa9cd9f7d0" + "libfaac/stats.h|1f5f6bf33735f861bca9f090b40282bfafeebecf5dc672e5640375a09361e32a" + "libfaac/stereo.c|2f5168bd1d95a4774514f46d1b9b32b09aab53479995ce2935c6e9bc83fe8e5e" + "libfaac/stereo.h|a893132aef72367b0b188e39e297b72a904f5b8c5d9082f9de2db920419ad1c3" + "libfaac/tns.c|9be4c7b00cf42f590e3d819c32429146a399fdafb65ba6ba52f9c79aa3571446" + "libfaac/tns.h|4d1c48722af6c5e9a2062330b3f54285236df76c50cf817a9a9cb9e4bca6f51e" + "libfaac/util.c|471d2fd0abcb09080e6a27692294bd6587b58961c248133bedda1505f9462d83" + "libfaac/util.h|c951a13f74684f2556e9afcad5c44adf2986b41a218e870393b3cb7e49392d0a" +) +set(FAAC_TRANSLATION_UNITS + "libfaac/bitstream.c" + "libfaac/blockswitch.c" + "libfaac/channels.c" + "libfaac/cpu_compute.c" + "libfaac/faac.c" + "libfaac/fft.c" + "libfaac/filtbank.c" + "libfaac/frame.c" + "libfaac/huff2.c" + "libfaac/huffdata.c" + "libfaac/quantize.c" + "libfaac/ratecontrol.c" + "libfaac/resample.c" + "libfaac/sbr.c" + "libfaac/sbr_analysis.c" + "libfaac/sbr_bitstream.c" + "libfaac/sbr_tables.c" + "libfaac/stereo.c" + "libfaac/tns.c" + "libfaac/util.c" +) diff --git a/patches/faac/manifest.json b/patches/faac/manifest.json new file mode 100644 index 000000000..1596e3e72 --- /dev/null +++ b/patches/faac/manifest.json @@ -0,0 +1,129 @@ +{ + "schema_version": 1, + "upstream_commit": "eb4928a76fd075f9bab20e8195df84f937da289b", + "patch_sha256": "b1284e6d79a6126f7efd37c19024bb47d4b9b9bf14fb791da7b5a9e68185b5ce", + "input_files": { + "COPYING": "20e50fe7aae3e56378ebf0417d9de904f55a0e61e4df315333e632a4d3555d95", + "include/faac.h": "e97ac5bc5c552c4f85df9a1807cc61a40cc56f9666aeb5f0f8c8958c4cf421e7", + "include/meson.build": "d3b6b545696ca89e653097a139d73231ab8c74ee6e8b6743227dd90fc962fff4", + "libfaac/atomic.h": "de2a1ea4e24c980c389ca1892502da79f4d245a01dbf944fffedcbc70464e43c", + "libfaac/bitstream.c": "0d8ec3510be0bade46fc39162cef81c8f30f3d46fa120ff340aa556de5a95d5e", + "libfaac/bitstream.h": "a3b70fffd0cd1bae2ef929888f9bf22928104ffb38d67740d859ba327b5c6904", + "libfaac/blockswitch.c": "7dc8e96a20b323cf5abdf3457a9da031b854f3ce7d9c42c4b7d8149fd5fa97b3", + "libfaac/blockswitch.h": "3981b58b9a70ad4994e4f7f852d5a48f94c5adfd974f475aa521d7d4f76cd80c", + "libfaac/channels.c": "db45f0b2c5e93ccba0233e36b8ab70477ea040d9b5b01462e6779a02794a8b86", + "libfaac/channels.h": "16087966954a71dfcfd75d9fa5bfc878ed62a5669f9a5e8bc283d7f2a07b3eeb", + "libfaac/coder.h": "2f77cf3be9654c3d358859dac08c4289145f293484621ae53bc9abf0aed6fc03", + "libfaac/cpu_compute.c": "8474a2e3c7a2a4e1ead63838d4e7210fce8366625bad737f295264c357fec630", + "libfaac/cpu_compute.h": "4137558c11e0a6bd695a1321f772ae848f927ba557bfcd225ae55834cb21034f", + "libfaac/faac.c": "29ca7a51f3cb06b00fc70e31ae5cad8cd6ef2828f14bdfb730a5b5431d5c68f6", + "libfaac/faac_internal.h": "f7c8eef5d06359e2bbd8fbea7828f76f829bbd6efe46e46992a7a3a3dbc35640", + "libfaac/fft.c": "90d109b6d4ead35f5c4eaba58afcffe246e2e38e7bfbe0b6106a29ff2abca1d1", + "libfaac/fft.h": "dff5196cff10275e71003385e947685b4bb5aa350fdb72371b3b683608e4e97b", + "libfaac/filtbank.c": "7248f5262003b302162114bfe05c433722abebc68080916f8371162b022eea34", + "libfaac/filtbank.h": "19e06c5eeb290e5daa7729125c7273bef9585d90a2674b48b7dd3f608b99fb3c", + "libfaac/frame.c": "174e7948bd59876b6be06913aaa0e340332173a2d408be45eee317c8009a0617", + "libfaac/frame.h": "1166b8ad39bf250cc215d6dde833121e9141f7b0219b6bb006e21c83f92404c9", + "libfaac/huff2.c": "4209e8e24fed7802ac7da61a08b17d80e005480277e848a103400f80b222e4e0", + "libfaac/huff2.h": "91491423c9843fa592bc42d64990cbdd1a98977fe498002524e177e4376ad17e", + "libfaac/huffdata.c": "1fa103248dcb7b01fbd1bc1469e4e6237a876529781b129b9cc09b191d3b69df", + "libfaac/huffdata.h": "7fd091d65e9d014c1b24d5dcaec88034e6010fb518bb933d7a383ad36ecc7f13", + "libfaac/libfaac.def": "1b79287f19960f816b18dcfc4671ffd0c1cccfaece22863c4124c9ce96b8d08c", + "libfaac/meson.build": "6aba38855395bce555a23086d642faedf2c3f503f164e43e8a935389396206c9", + "libfaac/quantize.c": "b490d95d4be952ea438752461e8e8bce9116c4375a90e575967afbe8e2f67443", + "libfaac/quantize.h": "6f9b624b9787866e87e48d77cbcc648c0e882b4e1831786faa617e9d4269d549", + "libfaac/quantize_sse.c": "39674732d1d98f6d92c85c58b3346cb835354bb5e5e477187ba1eb9db366820e", + "libfaac/ratecontrol.c": "64c80c165f0a2df845fc20dbf227408aea959f61907c44d7fd555da561cabbc7", + "libfaac/ratecontrol.h": "d7377ac08436f2285b24355eeceb83c2aa863fd2386bb2d9fe7b7040b4bd340d", + "libfaac/resample.c": "0004ce310b5c7bc320eb193cc85603c37aee085aef8456bca7593ac57c0a1b7e", + "libfaac/resample.h": "ef8382d80c4aa59909babba8b206ea63d585ee961f068f7c472241f25a56230a", + "libfaac/sbr.c": "bedaf0823ec78c7d0262246b89598b53ac45d22e2eaf2c033d68efc1b1afefeb", + "libfaac/sbr.h": "ace862bd8c63e225bd1d270f80daf0ca6ac2651c250cd9092d0ebc65907be20d", + "libfaac/sbr_analysis.c": "60641129ce70f014b577cba00cf9e05be50ca2cea763e35e9ca05fb262d8da38", + "libfaac/sbr_analysis.h": "73af1a1f8d045919a39b12c1156f0c07345864eac81aaa012c9e9cf378db8074", + "libfaac/sbr_bitstream.c": "c4c459bd28650820e56c105d0ffc92d4c2210dfd350bbda97802d711a3a5edd8", + "libfaac/sbr_internal.h": "0cdccb874908db29cb3b88e7b756e451a101399392289554241190defb2567da", + "libfaac/sbr_tables.c": "a698409114fbe2da36ede447422a985e91a0e7e53482ea8424949b0ba410e3e6", + "libfaac/sbr_tables.h": "262750f2ccd5058f135aae6fc36a856005fd530949d3a05bb5e4a1aa9cd9f7d0", + "libfaac/stats.h": "1f5f6bf33735f861bca9f090b40282bfafeebecf5dc672e5640375a09361e32a", + "libfaac/stereo.c": "2f5168bd1d95a4774514f46d1b9b32b09aab53479995ce2935c6e9bc83fe8e5e", + "libfaac/stereo.h": "a893132aef72367b0b188e39e297b72a904f5b8c5d9082f9de2db920419ad1c3", + "libfaac/tns.c": "9be4c7b00cf42f590e3d819c32429146a399fdafb65ba6ba52f9c79aa3571446", + "libfaac/tns.h": "4d1c48722af6c5e9a2062330b3f54285236df76c50cf817a9a9cb9e4bca6f51e", + "libfaac/util.c": "471d2fd0abcb09080e6a27692294bd6587b58961c248133bedda1505f9462d83", + "libfaac/util.h": "c951a13f74684f2556e9afcad5c44adf2986b41a218e870393b3cb7e49392d0a" + }, + "output_files": { + "COPYING": "20e50fe7aae3e56378ebf0417d9de904f55a0e61e4df315333e632a4d3555d95", + "include/faac.h": "e97ac5bc5c552c4f85df9a1807cc61a40cc56f9666aeb5f0f8c8958c4cf421e7", + "include/meson.build": "d3b6b545696ca89e653097a139d73231ab8c74ee6e8b6743227dd90fc962fff4", + "libfaac/atomic.h": "de2a1ea4e24c980c389ca1892502da79f4d245a01dbf944fffedcbc70464e43c", + "libfaac/bitstream.c": "0d8ec3510be0bade46fc39162cef81c8f30f3d46fa120ff340aa556de5a95d5e", + "libfaac/bitstream.h": "a3b70fffd0cd1bae2ef929888f9bf22928104ffb38d67740d859ba327b5c6904", + "libfaac/blockswitch.c": "7dc8e96a20b323cf5abdf3457a9da031b854f3ce7d9c42c4b7d8149fd5fa97b3", + "libfaac/blockswitch.h": "3981b58b9a70ad4994e4f7f852d5a48f94c5adfd974f475aa521d7d4f76cd80c", + "libfaac/channels.c": "db45f0b2c5e93ccba0233e36b8ab70477ea040d9b5b01462e6779a02794a8b86", + "libfaac/channels.h": "16087966954a71dfcfd75d9fa5bfc878ed62a5669f9a5e8bc283d7f2a07b3eeb", + "libfaac/coder.h": "2f77cf3be9654c3d358859dac08c4289145f293484621ae53bc9abf0aed6fc03", + "libfaac/cpu_compute.c": "8474a2e3c7a2a4e1ead63838d4e7210fce8366625bad737f295264c357fec630", + "libfaac/cpu_compute.h": "4137558c11e0a6bd695a1321f772ae848f927ba557bfcd225ae55834cb21034f", + "libfaac/faac.c": "e08d5f6710d0f7442edb4ec7953e9a596300ec566a1b173ec5ef9df0d66882fd", + "libfaac/faac_internal.h": "8a220de11ffde45f74fa18f5c8bab757ad0052671f4e20745433cd98935af6b1", + "libfaac/fft.c": "90d109b6d4ead35f5c4eaba58afcffe246e2e38e7bfbe0b6106a29ff2abca1d1", + "libfaac/fft.h": "dff5196cff10275e71003385e947685b4bb5aa350fdb72371b3b683608e4e97b", + "libfaac/filtbank.c": "7248f5262003b302162114bfe05c433722abebc68080916f8371162b022eea34", + "libfaac/filtbank.h": "19e06c5eeb290e5daa7729125c7273bef9585d90a2674b48b7dd3f608b99fb3c", + "libfaac/frame.c": "a1d01bd81c58f56b71fbfabdbb18ba7bc7326f2ed859236ec8a4ca629031a0bd", + "libfaac/frame.h": "1166b8ad39bf250cc215d6dde833121e9141f7b0219b6bb006e21c83f92404c9", + "libfaac/huff2.c": "4209e8e24fed7802ac7da61a08b17d80e005480277e848a103400f80b222e4e0", + "libfaac/huff2.h": "91491423c9843fa592bc42d64990cbdd1a98977fe498002524e177e4376ad17e", + "libfaac/huffdata.c": "1fa103248dcb7b01fbd1bc1469e4e6237a876529781b129b9cc09b191d3b69df", + "libfaac/huffdata.h": "7fd091d65e9d014c1b24d5dcaec88034e6010fb518bb933d7a383ad36ecc7f13", + "libfaac/libfaac.def": "1b79287f19960f816b18dcfc4671ffd0c1cccfaece22863c4124c9ce96b8d08c", + "libfaac/meson.build": "6aba38855395bce555a23086d642faedf2c3f503f164e43e8a935389396206c9", + "libfaac/quantize.c": "b490d95d4be952ea438752461e8e8bce9116c4375a90e575967afbe8e2f67443", + "libfaac/quantize.h": "6f9b624b9787866e87e48d77cbcc648c0e882b4e1831786faa617e9d4269d549", + "libfaac/quantize_sse.c": "39674732d1d98f6d92c85c58b3346cb835354bb5e5e477187ba1eb9db366820e", + "libfaac/ratecontrol.c": "64c80c165f0a2df845fc20dbf227408aea959f61907c44d7fd555da561cabbc7", + "libfaac/ratecontrol.h": "d7377ac08436f2285b24355eeceb83c2aa863fd2386bb2d9fe7b7040b4bd340d", + "libfaac/resample.c": "0004ce310b5c7bc320eb193cc85603c37aee085aef8456bca7593ac57c0a1b7e", + "libfaac/resample.h": "ef8382d80c4aa59909babba8b206ea63d585ee961f068f7c472241f25a56230a", + "libfaac/sbr.c": "bedaf0823ec78c7d0262246b89598b53ac45d22e2eaf2c033d68efc1b1afefeb", + "libfaac/sbr.h": "ace862bd8c63e225bd1d270f80daf0ca6ac2651c250cd9092d0ebc65907be20d", + "libfaac/sbr_analysis.c": "60641129ce70f014b577cba00cf9e05be50ca2cea763e35e9ca05fb262d8da38", + "libfaac/sbr_analysis.h": "73af1a1f8d045919a39b12c1156f0c07345864eac81aaa012c9e9cf378db8074", + "libfaac/sbr_bitstream.c": "c4c459bd28650820e56c105d0ffc92d4c2210dfd350bbda97802d711a3a5edd8", + "libfaac/sbr_internal.h": "0cdccb874908db29cb3b88e7b756e451a101399392289554241190defb2567da", + "libfaac/sbr_tables.c": "a698409114fbe2da36ede447422a985e91a0e7e53482ea8424949b0ba410e3e6", + "libfaac/sbr_tables.h": "262750f2ccd5058f135aae6fc36a856005fd530949d3a05bb5e4a1aa9cd9f7d0", + "libfaac/stats.h": "1f5f6bf33735f861bca9f090b40282bfafeebecf5dc672e5640375a09361e32a", + "libfaac/stereo.c": "2f5168bd1d95a4774514f46d1b9b32b09aab53479995ce2935c6e9bc83fe8e5e", + "libfaac/stereo.h": "a893132aef72367b0b188e39e297b72a904f5b8c5d9082f9de2db920419ad1c3", + "libfaac/tns.c": "9be4c7b00cf42f590e3d819c32429146a399fdafb65ba6ba52f9c79aa3571446", + "libfaac/tns.h": "4d1c48722af6c5e9a2062330b3f54285236df76c50cf817a9a9cb9e4bca6f51e", + "libfaac/util.c": "471d2fd0abcb09080e6a27692294bd6587b58961c248133bedda1505f9462d83", + "libfaac/util.h": "c951a13f74684f2556e9afcad5c44adf2986b41a218e870393b3cb7e49392d0a" + }, + "translation_units": [ + "libfaac/bitstream.c", + "libfaac/blockswitch.c", + "libfaac/channels.c", + "libfaac/cpu_compute.c", + "libfaac/faac.c", + "libfaac/fft.c", + "libfaac/filtbank.c", + "libfaac/frame.c", + "libfaac/huff2.c", + "libfaac/huffdata.c", + "libfaac/quantize.c", + "libfaac/ratecontrol.c", + "libfaac/resample.c", + "libfaac/sbr.c", + "libfaac/sbr_analysis.c", + "libfaac/sbr_bitstream.c", + "libfaac/sbr_tables.c", + "libfaac/stereo.c", + "libfaac/tns.c", + "libfaac/util.c" + ] +} diff --git a/playwright.config.ts b/playwright.config.ts index 47645df14..ab337e4e9 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -52,7 +52,7 @@ export default defineConfig({ }, // Output directory for test artifacts - outputDir: './test-results', + outputDir: process.env.LIGHTNVR_TEST_RESULTS_DIR || './test-results', // Global setup and teardown globalSetup: './tests/integration/global-setup.ts', diff --git a/quality/api-inventory.json b/quality/api-inventory.json new file mode 100644 index 000000000..1967e7fd0 --- /dev/null +++ b/quality/api-inventory.json @@ -0,0 +1,7963 @@ +{ + "schema_version": 1, + "task": "T2", + "generated_at": "2026-10-31", + "revision": "ff6a7d60", + "routing": { + "registration": "src/web/libuv_api_handlers.c register_all_libuv_handlers() (called from src/core/main.c); sole call site of http_server_register_handler besides its definition in src/web/libuv_server.c.", + "dispatch": "src/web/libuv_connection.c on_message_complete(): iterate handlers in registration order; skip when method differs (empty = any); first path_matches_pattern() hit wins. \"#\" matches a possibly-empty run of non-\"/\" characters, \"*\" matches the rest. Dot segments (incl. percent-encoded) are rejected with 400 before dispatch. /go2rtc and /go2rtc/* always go to the go2rtc proxy thread. No match => static_file_resolve_handler (web root, 404 JSON when missing).", + "backend": "HTTP_BACKEND=libuv is the only CMake option.", + "route_count": 239, + "shadowing_hazards": [], + "duplicate_registrations": {} + }, + "routes": [ + { + "order": 1, + "method": "GET", + "pattern": "/api/setup/status", + "handler": "handle_get_setup_status", + "registered_at": "src/web/libuv_api_handlers.c:96", + "handler_defined_at": "src/web/api_handlers_setup.c:33", + "ui_consumers": [ + "web/js/pages/index-page.jsx:41" + ], + "integration_tests_referencing_path": [ + "tests/integration/fixtures/test-fixtures.ts", + "tests/integration/specs/camera-favorites.ui.spec.ts", + "tests/integration/specs/detection-overlay-requests.ui.spec.ts", + "tests/integration/specs/live-inventory-refresh.ui.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 2, + "method": "POST", + "pattern": "/api/setup/status", + "handler": "handle_post_setup_complete", + "registered_at": "src/web/libuv_api_handlers.c:97", + "handler_defined_at": "src/web/api_handlers_setup.c:69", + "ui_consumers": [ + "web/js/components/preact/SetupWizard.jsx:281", + "web/js/components/preact/SetupWizard.jsx:330", + "web/js/components/preact/settings/GeneralTab.jsx:190" + ], + "integration_tests_referencing_path": [ + "tests/integration/fixtures/test-fixtures.ts", + "tests/integration/specs/camera-favorites.ui.spec.ts", + "tests/integration/specs/detection-overlay-requests.ui.spec.ts", + "tests/integration/specs/live-inventory-refresh.ui.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 3, + "method": "GET", + "pattern": "/api/health", + "handler": "handle_get_health", + "registered_at": "src/web/libuv_api_handlers.c:100", + "handler_defined_at": "src/web/api_handlers_health.c:238", + "ui_consumers": [ + "web/js/components/preact/HealthView.jsx:156", + "web/js/components/preact/HealthView.jsx:162", + "web/js/components/preact/system/WebServiceInfo.jsx:34" + ], + "integration_tests_referencing_path": [ + "tests/integration/specs/comprehensive.api.spec.ts", + "tests/integration/specs/system-health.api.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 4, + "method": "GET", + "pattern": "/api/health/hls", + "handler": "handle_get_hls_health", + "registered_at": "src/web/libuv_api_handlers.c:101", + "handler_defined_at": "src/web/api_handlers_health.c:533", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 5, + "method": "GET", + "pattern": "/api/metrics", + "handler": "handle_get_metrics", + "registered_at": "src/web/libuv_api_handlers.c:104", + "handler_defined_at": "src/web/api_handlers_metrics.c:1077", + "ui_consumers": [], + "integration_tests_referencing_path": [ + "tests/integration/specs/system-health-mqtt.api.spec.ts", + "tests/integration/specs/system-health.api.spec.ts" + ], + "has_ui_consumer": false + }, + { + "order": 6, + "method": "POST", + "pattern": "/api/telemetry/player", + "handler": "handle_post_player_telemetry", + "registered_at": "src/web/libuv_api_handlers.c:105", + "handler_defined_at": "src/web/api_handlers_metrics.c:1265", + "ui_consumers": [ + "web/js/utils/player-telemetry.js:37", + "web/js/utils/player-telemetry.js:39" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 7, + "method": "POST", + "pattern": "/api/motion/trigger", + "handler": "handle_post_motion_trigger", + "registered_at": "src/web/libuv_api_handlers.c:108", + "handler_defined_at": "src/web/api_handlers_motion.c:324", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 8, + "method": "GET", + "pattern": "/api/streams", + "handler": "handle_get_streams", + "registered_at": "src/web/libuv_api_handlers.c:111", + "handler_defined_at": "src/web/api_handlers_streams_get.c:444", + "ui_consumers": [ + "web/js/components/preact/StreamsView.jsx:199", + "web/js/utils/stream-summaries.js:24" + ], + "integration_tests_referencing_path": [ + "tests/integration/fixtures/test-fixtures.ts", + "tests/integration/go2rtc.api.spec.ts", + "tests/integration/lightnvr.api.spec.ts", + "tests/integration/pages/StreamsPage.ts", + "tests/integration/specs/camera-favorites.ui.spec.ts", + "tests/integration/specs/comprehensive.api.spec.ts", + "tests/integration/specs/detection-overlay-requests.ui.spec.ts", + "tests/integration/specs/live-inventory-refresh.ui.spec.ts", + "tests/integration/specs/liveview.ui.spec.ts", + "tests/integration/specs/stream-snapshot-requests.ui.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 9, + "method": "POST", + "pattern": "/api/streams", + "handler": "handle_post_stream", + "registered_at": "src/web/libuv_api_handlers.c:112", + "handler_defined_at": "src/web/api_handlers_streams_modify.c:562", + "ui_consumers": [ + "web/js/components/preact/StreamsView.jsx:358" + ], + "integration_tests_referencing_path": [ + "tests/integration/fixtures/test-fixtures.ts", + "tests/integration/go2rtc.api.spec.ts", + "tests/integration/lightnvr.api.spec.ts", + "tests/integration/pages/StreamsPage.ts", + "tests/integration/specs/camera-favorites.ui.spec.ts", + "tests/integration/specs/comprehensive.api.spec.ts", + "tests/integration/specs/detection-overlay-requests.ui.spec.ts", + "tests/integration/specs/live-inventory-refresh.ui.spec.ts", + "tests/integration/specs/liveview.ui.spec.ts", + "tests/integration/specs/stream-snapshot-requests.ui.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 10, + "method": "POST", + "pattern": "/api/streams/test", + "handler": "handle_test_stream", + "registered_at": "src/web/libuv_api_handlers.c:113", + "handler_defined_at": "src/web/api_handlers_streams_test.c:140", + "ui_consumers": [ + "web/js/components/preact/StreamsView.jsx:406" + ], + "integration_tests_referencing_path": [ + "tests/integration/specs/comprehensive.api.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 11, + "method": "GET", + "pattern": "/api/locations", + "handler": "handle_get_locations", + "registered_at": "src/web/libuv_api_handlers.c:116", + "handler_defined_at": "src/web/api_handlers_locations.c:272", + "ui_consumers": [ + "web/js/components/preact/EventRoutingView.jsx:125", + "web/js/components/preact/FleetView.jsx:151", + "web/js/components/preact/live/LiveOperatorNavigator.jsx:97", + "web/js/components/preact/settings/StorageTab.jsx:560", + "web/js/components/preact/users/AccessPolicyModal.jsx:186", + "web/js/components/preact/users/ApiKeyModal.jsx:149" + ], + "integration_tests_referencing_path": [ + "tests/integration/specs/api-tokens.ui.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 12, + "method": "POST", + "pattern": "/api/locations", + "handler": "handle_post_location", + "registered_at": "src/web/libuv_api_handlers.c:117", + "handler_defined_at": "src/web/api_handlers_locations.c:371", + "ui_consumers": [ + "web/js/components/preact/fleet/OrganizationManager.jsx:25" + ], + "integration_tests_referencing_path": [ + "tests/integration/specs/api-tokens.ui.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 13, + "method": "GET", + "pattern": "/api/locations/#", + "handler": "handle_get_location", + "registered_at": "src/web/libuv_api_handlers.c:118", + "handler_defined_at": "src/web/api_handlers_locations.c:398", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 14, + "method": "PUT", + "pattern": "/api/locations/#", + "handler": "handle_put_location", + "registered_at": "src/web/libuv_api_handlers.c:119", + "handler_defined_at": "src/web/api_handlers_locations.c:444", + "ui_consumers": [ + "web/js/components/preact/fleet/OrganizationManager.jsx:25" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 15, + "method": "DELETE", + "pattern": "/api/locations/#", + "handler": "handle_delete_location", + "registered_at": "src/web/libuv_api_handlers.c:120", + "handler_defined_at": "src/web/api_handlers_locations.c:476", + "ui_consumers": [ + "web/js/components/preact/fleet/OrganizationManager.jsx:89" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 16, + "method": "PUT", + "pattern": "/api/cameras/#/location", + "handler": "handle_put_camera_location", + "registered_at": "src/web/libuv_api_handlers.c:121", + "handler_defined_at": "src/web/api_handlers_locations.c:490", + "ui_consumers": [ + "web/js/components/preact/fleet/fleetOrganization.js:100" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 17, + "method": "GET", + "pattern": "/api/live/plans", + "handler": "handle_get_operator_floor_plans", + "registered_at": "src/web/libuv_api_handlers.c:125", + "handler_defined_at": "src/web/api_handlers_operator_floor_plans.c:634", + "ui_consumers": [ + "web/js/components/preact/live/LiveBuildingPlan.jsx:207", + "web/js/components/preact/live/LiveOperatorNavigator.jsx:107" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 18, + "method": "POST", + "pattern": "/api/live/plans", + "handler": "handle_post_operator_floor_plan", + "registered_at": "src/web/libuv_api_handlers.c:127", + "handler_defined_at": "src/web/api_handlers_operator_floor_plans.c:727", + "ui_consumers": [ + "web/js/components/preact/live/LiveBuildingPlan.jsx:318" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 19, + "method": "GET", + "pattern": "/api/live/plans/#/background", + "handler": "handle_get_operator_floor_plan_background", + "registered_at": "src/web/libuv_api_handlers.c:130", + "handler_defined_at": "src/web/api_handlers_operator_floor_plans.c:948", + "ui_consumers": [ + "web/js/components/preact/live/LiveBuildingPlan.jsx:67" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 20, + "method": "PUT", + "pattern": "/api/live/plans/#/background", + "handler": "handle_put_operator_floor_plan_background", + "registered_at": "src/web/libuv_api_handlers.c:132", + "handler_defined_at": "src/web/api_handlers_operator_floor_plans.c:985", + "ui_consumers": [ + "web/js/components/preact/live/LiveBuildingPlan.jsx:408" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 21, + "method": "DELETE", + "pattern": "/api/live/plans/#/background", + "handler": "handle_delete_operator_floor_plan_background", + "registered_at": "src/web/libuv_api_handlers.c:134", + "handler_defined_at": "src/web/api_handlers_operator_floor_plans.c:1092", + "ui_consumers": [ + "web/js/components/preact/live/LiveBuildingPlan.jsx:460" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 22, + "method": "GET", + "pattern": "/api/live/plans/#", + "handler": "handle_get_operator_floor_plan", + "registered_at": "src/web/libuv_api_handlers.c:137", + "handler_defined_at": "src/web/api_handlers_operator_floor_plans.c:686", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 23, + "method": "PUT", + "pattern": "/api/live/plans/#", + "handler": "handle_put_operator_floor_plan", + "registered_at": "src/web/libuv_api_handlers.c:139", + "handler_defined_at": "src/web/api_handlers_operator_floor_plans.c:760", + "ui_consumers": [ + "web/js/components/preact/live/LiveBuildingPlan.jsx:350", + "web/js/components/preact/live/LiveBuildingPlan.jsx:433", + "web/js/components/preact/live/LiveBuildingPlan.jsx:660" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 24, + "method": "DELETE", + "pattern": "/api/live/plans/#", + "handler": "handle_delete_operator_floor_plan", + "registered_at": "src/web/libuv_api_handlers.c:141", + "handler_defined_at": "src/web/api_handlers_operator_floor_plans.c:801", + "ui_consumers": [ + "web/js/components/preact/live/LiveBuildingPlan.jsx:371" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 25, + "method": "GET", + "pattern": "/api/camera-tags", + "handler": "handle_get_camera_tags", + "registered_at": "src/web/libuv_api_handlers.c:144", + "handler_defined_at": "src/web/api_handlers_camera_tags.c:216", + "ui_consumers": [ + "web/js/components/preact/EventRoutingView.jsx:126", + "web/js/components/preact/FleetView.jsx:159", + "web/js/components/preact/settings/StorageTab.jsx:561", + "web/js/components/preact/users/AccessPolicyModal.jsx:187", + "web/js/components/preact/users/ApiKeyModal.jsx:150" + ], + "integration_tests_referencing_path": [ + "tests/integration/specs/api-tokens.ui.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 26, + "method": "POST", + "pattern": "/api/camera-tags", + "handler": "handle_post_camera_tag", + "registered_at": "src/web/libuv_api_handlers.c:146", + "handler_defined_at": "src/web/api_handlers_camera_tags.c:284", + "ui_consumers": [ + "web/js/components/preact/fleet/OrganizationManager.jsx:177" + ], + "integration_tests_referencing_path": [ + "tests/integration/specs/api-tokens.ui.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 27, + "method": "POST", + "pattern": "/api/camera-tags/#/merge", + "handler": "handle_post_camera_tag_merge", + "registered_at": "src/web/libuv_api_handlers.c:148", + "handler_defined_at": "src/web/api_handlers_camera_tags.c:369", + "ui_consumers": [ + "web/js/components/preact/fleet/OrganizationManager.jsx:172" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 28, + "method": "GET", + "pattern": "/api/camera-tags/#", + "handler": "handle_get_camera_tag", + "registered_at": "src/web/libuv_api_handlers.c:150", + "handler_defined_at": "src/web/api_handlers_camera_tags.c:306", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 29, + "method": "PUT", + "pattern": "/api/camera-tags/#", + "handler": "handle_put_camera_tag", + "registered_at": "src/web/libuv_api_handlers.c:152", + "handler_defined_at": "src/web/api_handlers_camera_tags.c:329", + "ui_consumers": [ + "web/js/components/preact/fleet/OrganizationManager.jsx:177" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 30, + "method": "DELETE", + "pattern": "/api/camera-tags/#", + "handler": "handle_delete_camera_tag", + "registered_at": "src/web/libuv_api_handlers.c:154", + "handler_defined_at": "src/web/api_handlers_camera_tags.c:357", + "ui_consumers": [ + "web/js/components/preact/fleet/OrganizationManager.jsx:237" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 31, + "method": "GET", + "pattern": "/api/cameras/#/tags", + "handler": "handle_get_camera_tag_assignments", + "registered_at": "src/web/libuv_api_handlers.c:156", + "handler_defined_at": "src/web/api_handlers_camera_tags.c:453", + "ui_consumers": [ + "web/js/components/preact/fleet/fleetOrganization.js:110" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 32, + "method": "PUT", + "pattern": "/api/cameras/#/tags", + "handler": "handle_put_camera_tag_assignments", + "registered_at": "src/web/libuv_api_handlers.c:158", + "handler_defined_at": "src/web/api_handlers_camera_tags.c:463", + "ui_consumers": [ + "web/js/components/preact/fleet/fleetOrganization.js:122" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 33, + "method": "POST", + "pattern": "/api/fleet/cameras/query", + "handler": "handle_post_fleet_camera_query", + "registered_at": "src/web/libuv_api_handlers.c:162", + "handler_defined_at": "src/web/api_handlers_fleet.c:870", + "ui_consumers": [ + "web/js/components/preact/FleetView.jsx:134", + "web/js/components/preact/fleet/CollectionSelectorBuilder.jsx:52", + "web/js/components/preact/fleet/CollectionSelectorBuilder.jsx:88", + "web/js/components/preact/fleet/StaticCollectionMembers.jsx:29", + "web/js/components/preact/fleet/collectionMembership.js:14" + ], + "integration_tests_referencing_path": [ + "tests/integration/specs/camera-favorites.ui.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 34, + "method": "POST", + "pattern": "/api/fleet/selectors/preview", + "handler": "handle_post_fleet_selector_preview", + "registered_at": "src/web/libuv_api_handlers.c:164", + "handler_defined_at": "src/web/api_handlers_fleet.c:875", + "ui_consumers": [ + "web/js/components/preact/fleet/CollectionSelectorBuilder.jsx:306" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 35, + "method": "GET", + "pattern": "/api/fleet/views", + "handler": "handle_get_fleet_saved_views", + "registered_at": "src/web/libuv_api_handlers.c:166", + "handler_defined_at": "src/web/api_handlers_fleet_views.c:220", + "ui_consumers": [ + "web/js/components/preact/FleetView.jsx:173" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 36, + "method": "POST", + "pattern": "/api/fleet/views", + "handler": "handle_post_fleet_saved_view", + "registered_at": "src/web/libuv_api_handlers.c:168", + "handler_defined_at": "src/web/api_handlers_fleet_views.c:258", + "ui_consumers": [ + "web/js/components/preact/fleet/SavedViewsBar.jsx:33" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 37, + "method": "GET", + "pattern": "/api/fleet/views/#", + "handler": "handle_get_fleet_saved_view", + "registered_at": "src/web/libuv_api_handlers.c:170", + "handler_defined_at": "src/web/api_handlers_fleet_views.c:298", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 38, + "method": "PUT", + "pattern": "/api/fleet/views/#", + "handler": "handle_put_fleet_saved_view", + "registered_at": "src/web/libuv_api_handlers.c:172", + "handler_defined_at": "src/web/api_handlers_fleet_views.c:310", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 39, + "method": "DELETE", + "pattern": "/api/fleet/views/#", + "handler": "handle_delete_fleet_saved_view", + "registered_at": "src/web/libuv_api_handlers.c:174", + "handler_defined_at": "src/web/api_handlers_fleet_views.c:356", + "ui_consumers": [ + "web/js/components/preact/fleet/SavedViewsBar.jsx:54" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 40, + "method": "GET", + "pattern": "/api/live/layouts", + "handler": "handle_get_live_layouts", + "registered_at": "src/web/libuv_api_handlers.c:178", + "handler_defined_at": "src/web/api_handlers_live_layouts.c:286", + "ui_consumers": [ + "web/js/components/preact/live/LiveOperatorNavigator.jsx:102" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 41, + "method": "POST", + "pattern": "/api/live/layouts", + "handler": "handle_post_live_layout", + "registered_at": "src/web/libuv_api_handlers.c:180", + "handler_defined_at": "src/web/api_handlers_live_layouts.c:332", + "ui_consumers": [ + "web/js/components/preact/live/LiveOperatorNavigator.jsx:235" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 42, + "method": "PUT", + "pattern": "/api/live/layouts/#", + "handler": "handle_put_live_layout", + "registered_at": "src/web/libuv_api_handlers.c:182", + "handler_defined_at": "src/web/api_handlers_live_layouts.c:361", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 43, + "method": "DELETE", + "pattern": "/api/live/layouts/#", + "handler": "handle_delete_live_layout", + "registered_at": "src/web/libuv_api_handlers.c:184", + "handler_defined_at": "src/web/api_handlers_live_layouts.c:412", + "ui_consumers": [ + "web/js/components/preact/live/LiveOperatorNavigator.jsx:265" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 44, + "method": "GET", + "pattern": "/api/camera-favorites", + "handler": "handle_get_camera_favorites", + "registered_at": "src/web/libuv_api_handlers.c:188", + "handler_defined_at": "src/web/api_handlers_camera_favorites.c:121", + "ui_consumers": [ + "web/js/components/preact/live/cameraFavorites.js:47" + ], + "integration_tests_referencing_path": [ + "tests/integration/specs/camera-favorites.ui.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 45, + "method": "PUT", + "pattern": "/api/camera-favorites/#", + "handler": "handle_put_camera_favorite", + "registered_at": "src/web/libuv_api_handlers.c:190", + "handler_defined_at": "src/web/api_handlers_camera_favorites.c:166", + "ui_consumers": [ + "web/js/components/preact/live/cameraFavorites.js:41" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 46, + "method": "DELETE", + "pattern": "/api/camera-favorites/#", + "handler": "handle_delete_camera_favorite", + "registered_at": "src/web/libuv_api_handlers.c:192", + "handler_defined_at": "src/web/api_handlers_camera_favorites.c:199", + "ui_consumers": [ + "web/js/components/preact/live/cameraFavorites.js:41" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 47, + "method": "GET", + "pattern": "/api/camera-collections", + "handler": "handle_get_camera_collections", + "registered_at": "src/web/libuv_api_handlers.c:196", + "handler_defined_at": "src/web/api_handlers_camera_collections.c:366", + "ui_consumers": [ + "web/js/components/preact/FleetView.jsx:167", + "web/js/components/preact/RecordingsView.jsx:82", + "web/js/components/preact/fleet/collectionMembership.js:42", + "web/js/components/preact/users/AccessPolicyModal.jsx:185", + "web/js/components/preact/users/ApiKeyModal.jsx:148" + ], + "integration_tests_referencing_path": [ + "tests/integration/specs/api-tokens.ui.spec.ts", + "tests/integration/specs/camera-favorites.ui.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 48, + "method": "POST", + "pattern": "/api/camera-collections", + "handler": "handle_post_camera_collection", + "registered_at": "src/web/libuv_api_handlers.c:198", + "handler_defined_at": "src/web/api_handlers_camera_collections.c:450", + "ui_consumers": [ + "web/js/components/preact/fleet/CollectionManager.jsx:40" + ], + "integration_tests_referencing_path": [ + "tests/integration/specs/api-tokens.ui.spec.ts", + "tests/integration/specs/camera-favorites.ui.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 49, + "method": "GET", + "pattern": "/api/camera-collections/#/members", + "handler": "handle_get_camera_collection_members", + "registered_at": "src/web/libuv_api_handlers.c:200", + "handler_defined_at": "src/web/api_handlers_camera_collections.c:536", + "ui_consumers": [ + "web/js/components/preact/fleet/CollectionManager.jsx:193" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 50, + "method": "PUT", + "pattern": "/api/camera-collections/#/members", + "handler": "handle_put_camera_collection_members", + "registered_at": "src/web/libuv_api_handlers.c:202", + "handler_defined_at": "src/web/api_handlers_camera_collections.c:608", + "ui_consumers": [ + "web/js/components/preact/fleet/CollectionManager.jsx:48" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 51, + "method": "POST", + "pattern": "/api/camera-collections/#/preview", + "handler": "handle_post_camera_collection_preview", + "registered_at": "src/web/libuv_api_handlers.c:204", + "handler_defined_at": "src/web/api_handlers_camera_collections.c:665", + "ui_consumers": [ + "web/js/components/preact/fleet/CollectionManager.jsx:177" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 52, + "method": "GET", + "pattern": "/api/camera-collections/#", + "handler": "handle_get_camera_collection", + "registered_at": "src/web/libuv_api_handlers.c:206", + "handler_defined_at": "src/web/api_handlers_camera_collections.c:474", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 53, + "method": "PUT", + "pattern": "/api/camera-collections/#", + "handler": "handle_put_camera_collection", + "registered_at": "src/web/libuv_api_handlers.c:208", + "handler_defined_at": "src/web/api_handlers_camera_collections.c:495", + "ui_consumers": [ + "web/js/components/preact/fleet/CollectionManager.jsx:40" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 54, + "method": "DELETE", + "pattern": "/api/camera-collections/#", + "handler": "handle_delete_camera_collection", + "registered_at": "src/web/libuv_api_handlers.c:210", + "handler_defined_at": "src/web/api_handlers_camera_collections.c:523", + "ui_consumers": [ + "web/js/components/preact/fleet/CollectionManager.jsx:206" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 55, + "method": "GET", + "pattern": "/api/streams/#/recording", + "handler": "handle_get_stream_recording", + "registered_at": "src/web/libuv_api_handlers.c:214", + "handler_defined_at": "src/web/api_handlers_recording_control.c:90", + "ui_consumers": [ + "web/js/components/preact/ManualRecordingButton.jsx:10" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 56, + "method": "POST", + "pattern": "/api/streams/#/recording", + "handler": "handle_post_stream_recording", + "registered_at": "src/web/libuv_api_handlers.c:216", + "handler_defined_at": "src/web/api_handlers_recording_control.c:139", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 57, + "method": "GET", + "pattern": "/api/streams/#/zones", + "handler": "handle_get_zones", + "registered_at": "src/web/libuv_api_handlers.c:220", + "handler_defined_at": "src/web/api_handlers_zones.c:26", + "ui_consumers": [ + "web/js/components/preact/DetectionOverlay.jsx:43", + "web/js/components/preact/StreamConfigModal.jsx:336", + "web/js/components/preact/ZoneEditor.jsx:53" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 58, + "method": "POST", + "pattern": "/api/streams/#/zones", + "handler": "handle_post_zones", + "registered_at": "src/web/libuv_api_handlers.c:221", + "handler_defined_at": "src/web/api_handlers_zones.c:114", + "ui_consumers": [ + "web/js/components/preact/ZoneEditor.jsx:481" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 59, + "method": "DELETE", + "pattern": "/api/streams/#/zones", + "handler": "handle_delete_zones", + "registered_at": "src/web/libuv_api_handlers.c:222", + "handler_defined_at": "src/web/api_handlers_zones.c:255", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 60, + "method": "GET", + "pattern": "/api/streams/#/retention", + "handler": "handle_get_stream_retention", + "registered_at": "src/web/libuv_api_handlers.c:225", + "handler_defined_at": "src/web/api_handlers_retention.c:89", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 61, + "method": "PUT", + "pattern": "/api/streams/#/retention", + "handler": "handle_put_stream_retention", + "registered_at": "src/web/libuv_api_handlers.c:226", + "handler_defined_at": "src/web/api_handlers_retention.c:133", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 62, + "method": "GET", + "pattern": "/api/streams/#/detection-engines", + "handler": "handle_get_detection_engines", + "registered_at": "src/web/libuv_api_handlers.c:227", + "handler_defined_at": "src/web/api_handlers_detection_engines.c:87", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 63, + "method": "PUT", + "pattern": "/api/streams/#/detection-engines", + "handler": "handle_put_detection_engines", + "registered_at": "src/web/libuv_api_handlers.c:229", + "handler_defined_at": "src/web/api_handlers_detection_engines.c:140", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 64, + "method": "POST", + "pattern": "/api/streams/#/refresh", + "handler": "handle_post_stream_refresh", + "registered_at": "src/web/libuv_api_handlers.c:233", + "handler_defined_at": "src/web/api_handlers_streams_modify.c:2641", + "ui_consumers": [ + "web/js/components/preact/HLSVideoCell.jsx:153", + "web/js/components/preact/WebRTCVideoCell.jsx:1123" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 65, + "method": "GET", + "pattern": "/api/streams/#/ptz/capabilities", + "handler": "handle_ptz_capabilities", + "registered_at": "src/web/libuv_api_handlers.c:236", + "handler_defined_at": "src/web/api_handlers_ptz.c:815", + "ui_consumers": [ + "web/js/components/preact/ptzActions.js:2" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 66, + "method": "GET", + "pattern": "/api/streams/#/ptz/presets", + "handler": "handle_ptz_get_presets", + "registered_at": "src/web/libuv_api_handlers.c:237", + "handler_defined_at": "src/web/api_handlers_ptz.c:596", + "ui_consumers": [ + "web/js/components/preact/ptzActions.js:2" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 67, + "method": "POST", + "pattern": "/api/streams/#/ptz/move", + "handler": "handle_ptz_move", + "registered_at": "src/web/libuv_api_handlers.c:238", + "handler_defined_at": "src/web/api_handlers_ptz.c:135", + "ui_consumers": [ + "web/js/components/preact/PTZControls.jsx:17" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 68, + "method": "POST", + "pattern": "/api/streams/#/ptz/stop", + "handler": "handle_ptz_stop", + "registered_at": "src/web/libuv_api_handlers.c:239", + "handler_defined_at": "src/web/api_handlers_ptz.c:219", + "ui_consumers": [ + "web/js/components/preact/PTZControls.jsx:26" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 69, + "method": "POST", + "pattern": "/api/streams/#/ptz/absolute", + "handler": "handle_ptz_absolute", + "registered_at": "src/web/libuv_api_handlers.c:240", + "handler_defined_at": "src/web/api_handlers_ptz.c:278", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 70, + "method": "POST", + "pattern": "/api/streams/#/ptz/relative", + "handler": "handle_ptz_relative", + "registered_at": "src/web/libuv_api_handlers.c:241", + "handler_defined_at": "src/web/api_handlers_ptz.c:377", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 71, + "method": "POST", + "pattern": "/api/streams/#/ptz/home", + "handler": "handle_ptz_home", + "registered_at": "src/web/libuv_api_handlers.c:242", + "handler_defined_at": "src/web/api_handlers_ptz.c:476", + "ui_consumers": [ + "web/js/components/preact/PTZControls.jsx:33" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 72, + "method": "POST", + "pattern": "/api/streams/#/ptz/set-home", + "handler": "handle_ptz_set_home", + "registered_at": "src/web/libuv_api_handlers.c:243", + "handler_defined_at": "src/web/api_handlers_ptz.c:536", + "ui_consumers": [ + "web/js/components/preact/ptzActions.js:2" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 73, + "method": "POST", + "pattern": "/api/streams/#/ptz/goto-preset", + "handler": "handle_ptz_goto_preset", + "registered_at": "src/web/libuv_api_handlers.c:244", + "handler_defined_at": "src/web/api_handlers_ptz.c:654", + "ui_consumers": [ + "web/js/components/preact/PTZControls.jsx:40" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 74, + "method": "PUT", + "pattern": "/api/streams/#/ptz/preset", + "handler": "handle_ptz_set_preset", + "registered_at": "src/web/libuv_api_handlers.c:245", + "handler_defined_at": "src/web/api_handlers_ptz.c:735", + "ui_consumers": [ + "web/js/components/preact/ptzActions.js:2" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 75, + "method": "GET", + "pattern": "/api/streams/#/imaging/settings", + "handler": "handle_imaging_get_settings", + "registered_at": "src/web/libuv_api_handlers.c:248", + "handler_defined_at": "src/web/api_handlers_imaging.c:489", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 76, + "method": "PUT", + "pattern": "/api/streams/#/imaging/settings", + "handler": "handle_imaging_put_settings", + "registered_at": "src/web/libuv_api_handlers.c:249", + "handler_defined_at": "src/web/api_handlers_imaging.c:557", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 77, + "method": "GET", + "pattern": "/api/streams/#/imaging/options", + "handler": "handle_imaging_get_options", + "registered_at": "src/web/libuv_api_handlers.c:250", + "handler_defined_at": "src/web/api_handlers_imaging.c:523", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 78, + "method": "GET", + "pattern": "/api/streams/#/daynight", + "handler": "handle_daynight_get", + "registered_at": "src/web/libuv_api_handlers.c:251", + "handler_defined_at": "src/web/api_handlers_imaging.c:600", + "ui_consumers": [ + "web/js/components/preact/DayNightControl.jsx:69" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 79, + "method": "PUT", + "pattern": "/api/streams/#/daynight", + "handler": "handle_daynight_put", + "registered_at": "src/web/libuv_api_handlers.c:252", + "handler_defined_at": "src/web/api_handlers_imaging.c:654", + "ui_consumers": [ + "web/js/components/preact/DayNightControl.jsx:95" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 80, + "method": "GET", + "pattern": "/api/streams/#/full", + "handler": "handle_get_stream_full", + "registered_at": "src/web/libuv_api_handlers.c:255", + "handler_defined_at": "src/web/api_handlers_streams_get.c:951", + "ui_consumers": [ + "web/js/components/preact/StreamsView.jsx:808", + "web/js/components/preact/StreamsView.jsx:900" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 81, + "method": "GET", + "pattern": "/api/streams/#", + "handler": "handle_get_stream", + "registered_at": "src/web/libuv_api_handlers.c:256", + "handler_defined_at": "src/web/api_handlers_streams_get.c:748", + "ui_consumers": [], + "integration_tests_referencing_path": [ + "tests/integration/lightnvr.api.spec.ts", + "tests/integration/specs/comprehensive.api.spec.ts", + "tests/integration/specs/stream-snapshot-requests.ui.spec.ts" + ], + "has_ui_consumer": false + }, + { + "order": 82, + "method": "PUT", + "pattern": "/api/streams/#", + "handler": "handle_put_stream", + "registered_at": "src/web/libuv_api_handlers.c:257", + "handler_defined_at": "src/web/api_handlers_streams_modify.c:1251", + "ui_consumers": [ + "web/js/components/preact/HLSVideoCell.jsx:665", + "web/js/components/preact/HLSVideoCell.jsx:688", + "web/js/components/preact/MSEVideoCell.jsx:553", + "web/js/components/preact/MSEVideoCell.jsx:576", + "web/js/components/preact/StreamsView.jsx:343", + "web/js/components/preact/StreamsView.jsx:484", + "web/js/components/preact/StreamsView.jsx:532", + "web/js/components/preact/WebRTCVideoCell.jsx:1201", + "web/js/components/preact/WebRTCVideoCell.jsx:1224" + ], + "integration_tests_referencing_path": [ + "tests/integration/lightnvr.api.spec.ts", + "tests/integration/specs/comprehensive.api.spec.ts", + "tests/integration/specs/stream-snapshot-requests.ui.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 83, + "method": "DELETE", + "pattern": "/api/streams/#", + "handler": "handle_delete_stream", + "registered_at": "src/web/libuv_api_handlers.c:258", + "handler_defined_at": "src/web/api_handlers_streams_modify.c:2328", + "ui_consumers": [ + "web/js/components/preact/StreamsView.jsx:433", + "web/js/components/preact/StreamsView.jsx:459", + "web/js/components/preact/StreamsView.jsx:562", + "web/js/components/preact/StreamsView.jsx:583" + ], + "integration_tests_referencing_path": [ + "tests/integration/lightnvr.api.spec.ts", + "tests/integration/specs/comprehensive.api.spec.ts", + "tests/integration/specs/stream-snapshot-requests.ui.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 84, + "method": "GET", + "pattern": "/api/settings", + "handler": "handle_get_settings", + "registered_at": "src/web/libuv_api_handlers.c:261", + "handler_defined_at": "src/web/api_handlers_settings.c:705", + "ui_consumers": [ + "web/js/components/preact/EventRoutingView.jsx:124", + "web/js/components/preact/SettingsView.jsx:221", + "web/js/components/preact/SetupWizard.jsx:216", + "web/js/components/preact/StreamsView.jsx:999" + ], + "integration_tests_referencing_path": [ + "tests/integration/fixtures/test-fixtures.ts", + "tests/integration/lightnvr.api.spec.ts", + "tests/integration/specs/comprehensive.api.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 85, + "method": "GET", + "pattern": "/api/client-config", + "handler": "handle_get_client_config", + "registered_at": "src/web/libuv_api_handlers.c:262", + "handler_defined_at": "src/web/api_handlers_settings.c:641", + "ui_consumers": [ + "web/js/components/preact/RecordingsView.jsx:184", + "web/js/components/preact/StreamsView.jsx:229", + "web/js/pages/hls-page.jsx:36", + "web/js/pages/index-page.jsx:40", + "web/js/utils/settings-utils.js:37" + ], + "integration_tests_referencing_path": [ + "tests/integration/specs/camera-favorites.ui.spec.ts", + "tests/integration/specs/detection-overlay-requests.ui.spec.ts", + "tests/integration/specs/health-monitoring-state.ui.spec.ts", + "tests/integration/specs/live-inventory-refresh.ui.spec.ts", + "tests/integration/specs/stream-snapshot-requests.ui.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 86, + "method": "POST", + "pattern": "/api/settings", + "handler": "handle_post_settings", + "registered_at": "src/web/libuv_api_handlers.c:264", + "handler_defined_at": "src/web/api_handlers_settings.c:954", + "ui_consumers": [ + "web/js/components/preact/SettingsView.jsx:233", + "web/js/components/preact/SetupWizard.jsx:259" + ], + "integration_tests_referencing_path": [ + "tests/integration/fixtures/test-fixtures.ts", + "tests/integration/lightnvr.api.spec.ts", + "tests/integration/specs/comprehensive.api.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 87, + "method": "POST", + "pattern": "/api/settings/go2rtc/validate", + "handler": "handle_post_settings_go2rtc_validate", + "registered_at": "src/web/libuv_api_handlers.c:265", + "handler_defined_at": "src/web/api_handlers_settings.c:575", + "ui_consumers": [ + "web/js/components/preact/settings/Go2rtcTab.jsx:112" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 88, + "method": "GET", + "pattern": "/api/ui/workspaces", + "handler": "handle_get_ui_workspaces", + "registered_at": "src/web/libuv_api_handlers.c:267", + "handler_defined_at": "src/web/api_handlers_workspaces.c:77", + "ui_consumers": [ + "web/js/components/preact/live/LiveOperatorNavigator.jsx:92", + "web/js/components/preact/settings/WorkspacesTab.jsx:24" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 89, + "method": "PUT", + "pattern": "/api/ui/workspaces", + "handler": "handle_put_ui_workspaces", + "registered_at": "src/web/libuv_api_handlers.c:269", + "handler_defined_at": "src/web/api_handlers_workspaces.c:95", + "ui_consumers": [ + "web/js/components/preact/settings/WorkspacesTab.jsx:35" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 90, + "method": "GET", + "pattern": "/api/ice-servers", + "handler": "handle_get_ice_servers", + "registered_at": "src/web/libuv_api_handlers.c:273", + "handler_defined_at": "src/web/api_handlers_ice_servers.c:31", + "ui_consumers": [ + "web/js/components/preact/WebRTCVideoCell.jsx:347" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 91, + "method": "GET", + "pattern": "/api/system", + "handler": "handle_get_system_info", + "registered_at": "src/web/libuv_api_handlers.c:276", + "handler_defined_at": "src/web/api_handlers_system.c:694", + "ui_consumers": [], + "integration_tests_referencing_path": [ + "tests/integration/fixtures/test-fixtures.ts", + "tests/integration/lightnvr.api.spec.ts", + "tests/integration/specs/auth.api.spec.ts", + "tests/integration/specs/comprehensive.api.spec.ts" + ], + "has_ui_consumer": false + }, + { + "order": 92, + "method": "GET", + "pattern": "/api/system/info", + "handler": "handle_get_system_info", + "registered_at": "src/web/libuv_api_handlers.c:277", + "handler_defined_at": "src/web/api_handlers_system.c:694", + "ui_consumers": [ + "web/js/components/preact/SetupWizard.jsx:234", + "web/js/components/preact/SystemView.jsx:139" + ], + "integration_tests_referencing_path": [ + "tests/integration/lightnvr.api.spec.ts", + "tests/integration/specs/health-monitoring-state.ui.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 93, + "method": "GET", + "pattern": "/api/system/health/incidents", + "handler": "handle_get_system_health_incidents", + "registered_at": "src/web/libuv_api_handlers.c:278", + "handler_defined_at": "src/web/api_handlers_system_health.c:860", + "ui_consumers": [ + "web/js/components/preact/system/HealthIncidentHistory.jsx:10" + ], + "integration_tests_referencing_path": [ + "tests/integration/specs/health-monitoring-state.ui.spec.ts", + "tests/integration/specs/system-health.api.spec.ts", + "tests/integration/specs/system-health.ui.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 94, + "method": "GET", + "pattern": "/api/system/health", + "handler": "handle_get_system_health", + "registered_at": "src/web/libuv_api_handlers.c:280", + "handler_defined_at": "src/web/api_handlers_system_health.c:499", + "ui_consumers": [ + "web/js/components/preact/system/SystemHealth.jsx:184" + ], + "integration_tests_referencing_path": [ + "tests/integration/specs/health-monitoring-state.ui.spec.ts", + "tests/integration/specs/system-health-mqtt.api.spec.ts", + "tests/integration/specs/system-health.api.spec.ts", + "tests/integration/specs/system-health.ui.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 95, + "method": "GET", + "pattern": "/api/system/logs", + "handler": "handle_get_system_logs", + "registered_at": "src/web/libuv_api_handlers.c:282", + "handler_defined_at": "src/web/api_handlers_system_logs.c:98", + "ui_consumers": [ + "web/js/components/preact/system/LogsPoller.jsx:57" + ], + "integration_tests_referencing_path": [ + "tests/integration/specs/comprehensive.api.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 96, + "method": "POST", + "pattern": "/api/system/restart", + "handler": "handle_post_system_restart", + "registered_at": "src/web/libuv_api_handlers.c:283", + "handler_defined_at": "src/web/api_handlers_system.c:1106", + "ui_consumers": [ + "web/js/components/preact/SystemView.jsx:199" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 97, + "method": "POST", + "pattern": "/api/system/shutdown", + "handler": "handle_post_system_shutdown", + "registered_at": "src/web/libuv_api_handlers.c:284", + "handler_defined_at": "src/web/api_handlers_system.c:1166", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 98, + "method": "POST", + "pattern": "/api/system/logs/clear", + "handler": "handle_post_system_logs_clear", + "registered_at": "src/web/libuv_api_handlers.c:285", + "handler_defined_at": "src/web/api_handlers_system_logs.c:174", + "ui_consumers": [ + "web/js/components/preact/SystemView.jsx:151" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 99, + "method": "POST", + "pattern": "/api/system/backup", + "handler": "handle_post_system_backup", + "registered_at": "src/web/libuv_api_handlers.c:286", + "handler_defined_at": "src/web/api_handlers_system.c:1254", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 100, + "method": "GET", + "pattern": "/api/system/status", + "handler": "handle_get_system_status", + "registered_at": "src/web/libuv_api_handlers.c:287", + "handler_defined_at": "src/web/api_handlers_system.c:1504", + "ui_consumers": [ + "web/js/components/preact/system/RestartModal.jsx:54", + "web/js/components/preact/system/RestartModal.jsx:90" + ], + "integration_tests_referencing_path": [ + "tests/integration/specs/comprehensive.api.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 101, + "method": "GET", + "pattern": "/api/system/go2rtc/effective-config", + "handler": "handle_get_system_go2rtc_effective_config", + "registered_at": "src/web/libuv_api_handlers.c:288", + "handler_defined_at": "src/web/api_handlers_system_go2rtc.c:184", + "ui_consumers": [ + "web/js/components/preact/settings/Go2rtcTab.jsx:155" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 102, + "method": "GET", + "pattern": "/api/system/go2rtc/override-status", + "handler": "handle_get_system_go2rtc_override_status", + "registered_at": "src/web/libuv_api_handlers.c:290", + "handler_defined_at": "src/web/api_handlers_system_go2rtc.c:371", + "ui_consumers": [ + "web/js/components/preact/settings/Go2rtcTab.jsx:156" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 103, + "method": "GET", + "pattern": "/api/detection/results/#", + "handler": "handle_get_detection_results", + "registered_at": "src/web/libuv_api_handlers.c:294", + "handler_defined_at": "src/web/api_handlers_detection.c:43", + "ui_consumers": [ + "web/js/components/preact/UI.jsx:320", + "web/js/components/preact/recordings/recordingsAPI.jsx:630", + "web/js/components/preact/recordings/recordingsAPI.jsx:661", + "web/js/components/preact/timeline/TimelinePlayer.jsx:629", + "web/js/utils/detection-polling.js:25" + ], + "integration_tests_referencing_path": [ + "tests/integration/specs/detection-overlay-requests.ui.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 104, + "method": "GET", + "pattern": "/api/detection/models", + "handler": "handle_get_detection_models", + "registered_at": "src/web/libuv_api_handlers.c:295", + "handler_defined_at": "src/web/api_handlers_detection_models.c:39", + "ui_consumers": [ + "web/js/components/preact/StreamsView.jsx:221" + ], + "integration_tests_referencing_path": [ + "tests/integration/specs/stream-snapshot-requests.ui.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 105, + "method": "POST", + "pattern": "/api/lpr/search", + "handler": "handle_post_lpr_search", + "registered_at": "src/web/libuv_api_handlers.c:296", + "handler_defined_at": "src/web/api_handlers_lpr.c:302", + "ui_consumers": [ + "web/js/components/preact/lpr/lprSearch.js:172" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 106, + "method": "POST", + "pattern": "/api/lpr/export", + "handler": "handle_post_lpr_export", + "registered_at": "src/web/libuv_api_handlers.c:297", + "handler_defined_at": "src/web/api_handlers_lpr.c:306", + "ui_consumers": [ + "web/js/components/preact/lpr/lprSearch.js:180" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 107, + "method": "DELETE", + "pattern": "/api/lpr/reads/#", + "handler": "handle_delete_lpr_read", + "registered_at": "src/web/libuv_api_handlers.c:298", + "handler_defined_at": "src/web/api_handlers_lpr.c:310", + "ui_consumers": [ + "web/js/components/preact/lpr/lprSearch.js:187" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 108, + "method": "GET", + "pattern": "/api/snapshots/#/#", + "handler": "handle_get_detection_snapshot", + "registered_at": "src/web/libuv_api_handlers.c:300", + "handler_defined_at": "src/web/api_handlers_detection_results.c:159", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 109, + "method": "GET", + "pattern": "/api/storage/health", + "handler": "handle_get_storage_health", + "registered_at": "src/web/libuv_api_handlers.c:303", + "handler_defined_at": "src/web/api_handlers_storage.c:36", + "ui_consumers": [ + "web/js/components/preact/system/StorageHealth.jsx:60" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 110, + "method": "POST", + "pattern": "/api/storage/cleanup", + "handler": "handle_post_storage_cleanup", + "registered_at": "src/web/libuv_api_handlers.c:304", + "handler_defined_at": "src/web/api_handlers_storage.c:131", + "ui_consumers": [ + "web/js/components/preact/system/StorageHealth.jsx:67" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 111, + "method": "GET", + "pattern": "/api/auth/login/config", + "handler": "handle_auth_login_config", + "registered_at": "src/web/libuv_api_handlers.c:307", + "handler_defined_at": "src/web/api_handlers_auth_backend_agnostic.c:657", + "ui_consumers": [ + "web/js/components/preact/LoginView.jsx:80" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 112, + "method": "POST", + "pattern": "/api/auth/login", + "handler": "handle_auth_login", + "registered_at": "src/web/libuv_api_handlers.c:308", + "handler_defined_at": "src/web/api_handlers_auth_backend_agnostic.c:223", + "ui_consumers": [ + "web/js/components/preact/LoginView.jsx:178" + ], + "integration_tests_referencing_path": [ + "tests/integration/fixtures/test-fixtures.ts", + "tests/integration/specs/auth.api.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 113, + "method": "POST", + "pattern": "/api/auth/logout", + "handler": "handle_auth_logout", + "registered_at": "src/web/libuv_api_handlers.c:309", + "handler_defined_at": "src/web/api_handlers_auth_backend_agnostic.c:534", + "ui_consumers": [ + "web/js/components/preact/LoginView.jsx:71" + ], + "integration_tests_referencing_path": [ + "tests/integration/fixtures/test-fixtures.ts", + "tests/integration/specs/auth.api.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 114, + "method": "GET", + "pattern": "/api/auth/verify", + "handler": "handle_auth_verify", + "registered_at": "src/web/libuv_api_handlers.c:310", + "handler_defined_at": "src/web/api_handlers_auth_backend_agnostic.c:575", + "ui_consumers": [ + "web/js/utils/auth-utils.js:48" + ], + "integration_tests_referencing_path": [ + "tests/integration/fixtures/test-fixtures.ts", + "tests/integration/specs/auth.api.spec.ts", + "tests/integration/specs/camera-favorites.ui.spec.ts", + "tests/integration/specs/detection-overlay-requests.ui.spec.ts", + "tests/integration/specs/health-monitoring-state.ui.spec.ts", + "tests/integration/specs/live-inventory-refresh.ui.spec.ts", + "tests/integration/specs/stream-snapshot-requests.ui.spec.ts", + "tests/integration/specs/system-health.ui.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 115, + "method": "GET", + "pattern": "/api/auth/sessions", + "handler": "handle_auth_sessions_list", + "registered_at": "src/web/libuv_api_handlers.c:311", + "handler_defined_at": "src/web/api_handlers_auth_backend_agnostic.c:673", + "ui_consumers": [ + "web/js/components/preact/SettingsView.jsx:267" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 116, + "method": "DELETE", + "pattern": "/api/auth/sessions/#", + "handler": "handle_auth_sessions_delete", + "registered_at": "src/web/libuv_api_handlers.c:312", + "handler_defined_at": "src/web/api_handlers_auth_backend_agnostic.c:711", + "ui_consumers": [ + "web/js/components/preact/SettingsView.jsx:287" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 117, + "method": "GET", + "pattern": "/api/auth/trusted-devices", + "handler": "handle_auth_trusted_devices_list", + "registered_at": "src/web/libuv_api_handlers.c:313", + "handler_defined_at": "src/web/api_handlers_auth_backend_agnostic.c:749", + "ui_consumers": [ + "web/js/components/preact/SettingsView.jsx:279" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 118, + "method": "DELETE", + "pattern": "/api/auth/trusted-devices/#", + "handler": "handle_auth_trusted_devices_delete", + "registered_at": "src/web/libuv_api_handlers.c:314", + "handler_defined_at": "src/web/api_handlers_auth_backend_agnostic.c:789", + "ui_consumers": [ + "web/js/components/preact/SettingsView.jsx:299" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 119, + "method": "GET", + "pattern": "/logout", + "handler": "handle_auth_logout", + "registered_at": "src/web/libuv_api_handlers.c:315", + "handler_defined_at": "src/web/api_handlers_auth_backend_agnostic.c:534", + "ui_consumers": [ + "web/js/components/preact/Header.jsx:306" + ], + "integration_tests_referencing_path": [ + "tests/integration/fixtures/test-fixtures.ts", + "tests/integration/pages/BasePage.ts" + ], + "has_ui_consumer": true + }, + { + "order": 120, + "method": "GET", + "pattern": "/api/auth/users", + "handler": "handle_users_list", + "registered_at": "src/web/libuv_api_handlers.c:318", + "handler_defined_at": "src/web/api_handlers_users_backend_agnostic.c:238", + "ui_consumers": [ + "web/js/components/preact/UsersView.jsx:64" + ], + "integration_tests_referencing_path": [ + "tests/integration/fixtures/test-fixtures.ts", + "tests/integration/specs/api-tokens.ui.spec.ts", + "tests/integration/specs/auth.api.spec.ts", + "tests/integration/specs/roles.ui.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 121, + "method": "POST", + "pattern": "/api/auth/users", + "handler": "handle_users_create", + "registered_at": "src/web/libuv_api_handlers.c:319", + "handler_defined_at": "src/web/api_handlers_users_backend_agnostic.c:340", + "ui_consumers": [ + "web/js/components/preact/UsersView.jsx:115" + ], + "integration_tests_referencing_path": [ + "tests/integration/fixtures/test-fixtures.ts", + "tests/integration/specs/api-tokens.ui.spec.ts", + "tests/integration/specs/auth.api.spec.ts", + "tests/integration/specs/roles.ui.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 122, + "method": "GET", + "pattern": "/api/auth/users/#", + "handler": "handle_users_get", + "registered_at": "src/web/libuv_api_handlers.c:320", + "handler_defined_at": "src/web/api_handlers_users_backend_agnostic.c:291", + "ui_consumers": [], + "integration_tests_referencing_path": [ + "tests/integration/specs/auth.api.spec.ts" + ], + "has_ui_consumer": false + }, + { + "order": 123, + "method": "PUT", + "pattern": "/api/auth/users/#", + "handler": "handle_users_update", + "registered_at": "src/web/libuv_api_handlers.c:321", + "handler_defined_at": "src/web/api_handlers_users_backend_agnostic.c:498", + "ui_consumers": [ + "web/js/components/preact/Header.jsx:177", + "web/js/components/preact/UsersView.jsx:144" + ], + "integration_tests_referencing_path": [ + "tests/integration/specs/auth.api.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 124, + "method": "DELETE", + "pattern": "/api/auth/users/#", + "handler": "handle_users_delete", + "registered_at": "src/web/libuv_api_handlers.c:322", + "handler_defined_at": "src/web/api_handlers_users_backend_agnostic.c:708", + "ui_consumers": [ + "web/js/components/preact/UsersView.jsx:173" + ], + "integration_tests_referencing_path": [ + "tests/integration/specs/auth.api.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 125, + "method": "POST", + "pattern": "/api/auth/users/#/api-key", + "handler": "handle_users_generate_api_key", + "registered_at": "src/web/libuv_api_handlers.c:323", + "handler_defined_at": "src/web/api_handlers_users_backend_agnostic.c:798", + "ui_consumers": [ + "web/js/components/preact/users/ApiKeyModal.jsx:242" + ], + "integration_tests_referencing_path": [ + "tests/integration/specs/auth.api.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 126, + "method": "PUT", + "pattern": "/api/auth/users/#/password", + "handler": "handle_users_change_password", + "registered_at": "src/web/libuv_api_handlers.c:324", + "handler_defined_at": "src/web/api_handlers_users_backend_agnostic.c:858", + "ui_consumers": [ + "web/js/components/preact/AuthGate.jsx:38" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 127, + "method": "PUT", + "pattern": "/api/auth/users/#/password-lock", + "handler": "handle_users_password_lock", + "registered_at": "src/web/libuv_api_handlers.c:325", + "handler_defined_at": "src/web/api_handlers_users_backend_agnostic.c:994", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 128, + "method": "POST", + "pattern": "/api/auth/users/#/login-lockout/clear", + "handler": "handle_users_clear_login_lockout", + "registered_at": "src/web/libuv_api_handlers.c:326", + "handler_defined_at": "src/web/api_handlers_users_backend_agnostic.c:1072", + "ui_consumers": [ + "web/js/components/preact/UsersView.jsx:197" + ], + "integration_tests_referencing_path": [ + "tests/integration/specs/auth.api.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 129, + "method": "GET", + "pattern": "/api/authorization/actions", + "handler": "handle_get_authorization_actions", + "registered_at": "src/web/libuv_api_handlers.c:329", + "handler_defined_at": "src/web/api_handlers_authorization.c:79", + "ui_consumers": [ + "web/js/components/preact/users/AccessPolicyModal.jsx:182", + "web/js/components/preact/users/ApiKeyModal.jsx:146", + "web/js/components/preact/users/RoleManagerModal.jsx:41" + ], + "integration_tests_referencing_path": [ + "tests/integration/specs/api-tokens.ui.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 130, + "method": "POST", + "pattern": "/api/authorization/simulate", + "handler": "handle_post_authorization_simulate", + "registered_at": "src/web/libuv_api_handlers.c:331", + "handler_defined_at": "src/web/api_handlers_authorization.c:125", + "ui_consumers": [ + "web/js/components/preact/users/AccessPolicyModal.jsx:110" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 131, + "method": "GET", + "pattern": "/api/authorization/roles", + "handler": "handle_get_authorization_roles", + "registered_at": "src/web/libuv_api_handlers.c:333", + "handler_defined_at": "src/web/api_handlers_authorization.c:519", + "ui_consumers": [ + "web/js/components/preact/users/AccessPolicyModal.jsx:183", + "web/js/components/preact/users/RoleManagerModal.jsx:42" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 132, + "method": "POST", + "pattern": "/api/authorization/roles", + "handler": "handle_post_authorization_role", + "registered_at": "src/web/libuv_api_handlers.c:335", + "handler_defined_at": "src/web/api_handlers_authorization.c:655", + "ui_consumers": [ + "web/js/components/preact/users/RoleManagerModal.jsx:101" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 133, + "method": "PUT", + "pattern": "/api/authorization/roles/#", + "handler": "handle_put_authorization_role", + "registered_at": "src/web/libuv_api_handlers.c:337", + "handler_defined_at": "src/web/api_handlers_authorization.c:690", + "ui_consumers": [ + "web/js/components/preact/users/RoleManagerModal.jsx:101" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 134, + "method": "DELETE", + "pattern": "/api/authorization/roles/#", + "handler": "handle_delete_authorization_role", + "registered_at": "src/web/libuv_api_handlers.c:339", + "handler_defined_at": "src/web/api_handlers_authorization.c:739", + "ui_consumers": [ + "web/js/components/preact/users/RoleManagerModal.jsx:129" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 135, + "method": "GET", + "pattern": "/api/authorization/users/#/tokens", + "handler": "handle_get_user_api_tokens", + "registered_at": "src/web/libuv_api_handlers.c:341", + "handler_defined_at": "src/web/api_handlers_authorization.c:1423", + "ui_consumers": [ + "web/js/components/preact/users/ApiKeyModal.jsx:147" + ], + "integration_tests_referencing_path": [ + "tests/integration/specs/api-tokens.ui.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 136, + "method": "POST", + "pattern": "/api/authorization/users/#/tokens", + "handler": "handle_post_user_api_token", + "registered_at": "src/web/libuv_api_handlers.c:344", + "handler_defined_at": "src/web/api_handlers_authorization.c:1599", + "ui_consumers": [ + "web/js/components/preact/users/ApiKeyModal.jsx:196" + ], + "integration_tests_referencing_path": [ + "tests/integration/specs/api-tokens.ui.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 137, + "method": "DELETE", + "pattern": "/api/authorization/users/#/tokens/#", + "handler": "handle_delete_user_api_token", + "registered_at": "src/web/libuv_api_handlers.c:347", + "handler_defined_at": "src/web/api_handlers_authorization.c:1745", + "ui_consumers": [ + "web/js/components/preact/users/ApiKeyModal.jsx:223" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 138, + "method": "GET", + "pattern": "/api/authorization/users/#", + "handler": "handle_get_user_authorization", + "registered_at": "src/web/libuv_api_handlers.c:350", + "handler_defined_at": "src/web/api_handlers_authorization.c:857", + "ui_consumers": [ + "web/js/components/preact/users/AccessPolicyModal.jsx:184" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 139, + "method": "PUT", + "pattern": "/api/authorization/users/#", + "handler": "handle_put_user_authorization", + "registered_at": "src/web/libuv_api_handlers.c:352", + "handler_defined_at": "src/web/api_handlers_authorization.c:1132", + "ui_consumers": [ + "web/js/components/preact/users/AccessPolicyModal.jsx:246" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 140, + "method": "GET", + "pattern": "/api/audit/events/export", + "handler": "handle_get_audit_export", + "registered_at": "src/web/libuv_api_handlers.c:356", + "handler_defined_at": "src/web/api_handlers_audit.c:216", + "ui_consumers": [ + "web/js/components/preact/users/AuditHistoryModal.jsx:198" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 141, + "method": "GET", + "pattern": "/api/audit/events", + "handler": "handle_get_audit_events", + "registered_at": "src/web/libuv_api_handlers.c:358", + "handler_defined_at": "src/web/api_handlers_audit.c:182", + "ui_consumers": [ + "web/js/components/preact/users/AuditHistoryModal.jsx:103" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 142, + "method": "GET", + "pattern": "/api/audit/settings", + "handler": "handle_get_audit_settings", + "registered_at": "src/web/libuv_api_handlers.c:360", + "handler_defined_at": "src/web/api_handlers_audit.c:323", + "ui_consumers": [ + "web/js/components/preact/users/AuditHistoryModal.jsx:121" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 143, + "method": "PUT", + "pattern": "/api/audit/settings", + "handler": "handle_put_audit_settings", + "registered_at": "src/web/libuv_api_handlers.c:362", + "handler_defined_at": "src/web/api_handlers_audit.c:335", + "ui_consumers": [ + "web/js/components/preact/users/AuditDecisionModes.jsx:31", + "web/js/components/preact/users/AuditHistoryModal.jsx:176" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 144, + "method": "GET", + "pattern": "/api/events/catalog", + "handler": "handle_get_event_catalog", + "registered_at": "src/web/libuv_api_handlers.c:366", + "handler_defined_at": "src/web/api_handlers_event_routes.c:478", + "ui_consumers": [ + "web/js/components/preact/EventRoutingView.jsx:123" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 145, + "method": "GET", + "pattern": "/api/event-destinations", + "handler": "handle_get_event_destinations", + "registered_at": "src/web/libuv_api_handlers.c:368", + "handler_defined_at": "src/web/api_handlers_event_destinations.c:416", + "ui_consumers": [ + "web/js/components/preact/EventRoutingView.jsx:122" + ], + "integration_tests_referencing_path": [ + "tests/integration/specs/system-health-mqtt.api.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 146, + "method": "POST", + "pattern": "/api/event-destinations", + "handler": "handle_post_event_destination", + "registered_at": "src/web/libuv_api_handlers.c:370", + "handler_defined_at": "src/web/api_handlers_event_destinations.c:474", + "ui_consumers": [ + "web/js/components/preact/EventRoutingView.jsx:182" + ], + "integration_tests_referencing_path": [ + "tests/integration/specs/system-health-mqtt.api.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 147, + "method": "GET", + "pattern": "/api/event-destinations/#", + "handler": "handle_get_event_destination", + "registered_at": "src/web/libuv_api_handlers.c:372", + "handler_defined_at": "src/web/api_handlers_event_destinations.c:515", + "ui_consumers": [], + "integration_tests_referencing_path": [ + "tests/integration/specs/system-health-mqtt.api.spec.ts" + ], + "has_ui_consumer": false + }, + { + "order": 148, + "method": "PUT", + "pattern": "/api/event-destinations/#", + "handler": "handle_put_event_destination", + "registered_at": "src/web/libuv_api_handlers.c:374", + "handler_defined_at": "src/web/api_handlers_event_destinations.c:531", + "ui_consumers": [ + "web/js/components/preact/EventRoutingView.jsx:182", + "web/js/components/preact/EventRoutingView.jsx:190" + ], + "integration_tests_referencing_path": [ + "tests/integration/specs/system-health-mqtt.api.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 149, + "method": "DELETE", + "pattern": "/api/event-destinations/#", + "handler": "handle_delete_event_destination", + "registered_at": "src/web/libuv_api_handlers.c:376", + "handler_defined_at": "src/web/api_handlers_event_destinations.c:596", + "ui_consumers": [ + "web/js/components/preact/EventRoutingView.jsx:194" + ], + "integration_tests_referencing_path": [ + "tests/integration/specs/system-health-mqtt.api.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 150, + "method": "GET", + "pattern": "/api/storage-archive", + "handler": "handle_get_storage_archive", + "registered_at": "src/web/libuv_api_handlers.c:379", + "handler_defined_at": "src/web/api_handlers_storage_migrations.c:318", + "ui_consumers": [ + "web/js/components/preact/settings/StorageTab.jsx:407" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 151, + "method": "POST", + "pattern": "/api/storage-archive/retry", + "handler": "handle_post_storage_archive_retry", + "registered_at": "src/web/libuv_api_handlers.c:380", + "handler_defined_at": "src/web/api_handlers_storage_migrations.c:367", + "ui_consumers": [ + "web/js/components/preact/settings/StorageTab.jsx:414" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 152, + "method": "POST", + "pattern": "/api/storage-policies/#/apply", + "handler": "handle_post_storage_policy_apply", + "registered_at": "src/web/libuv_api_handlers.c:381", + "handler_defined_at": "src/web/api_handlers_storage_policies.c:820", + "ui_consumers": [ + "web/js/components/preact/settings/StorageTab.jsx:569" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 153, + "method": "GET", + "pattern": "/api/storage-targets", + "handler": "handle_get_storage_targets", + "registered_at": "src/web/libuv_api_handlers.c:384", + "handler_defined_at": "src/web/api_handlers_storage_targets.c:382", + "ui_consumers": [ + "web/js/components/preact/settings/StorageTab.jsx:191", + "web/js/components/preact/settings/StorageTab.jsx:358", + "web/js/components/preact/settings/StorageTab.jsx:558" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 154, + "method": "POST", + "pattern": "/api/storage-targets", + "handler": "handle_post_storage_target", + "registered_at": "src/web/libuv_api_handlers.c:386", + "handler_defined_at": "src/web/api_handlers_storage_targets.c:439", + "ui_consumers": [ + "web/js/components/preact/settings/StorageTab.jsx:237" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 155, + "method": "POST", + "pattern": "/api/storage-targets/#/probe", + "handler": "handle_post_storage_target_probe", + "registered_at": "src/web/libuv_api_handlers.c:388", + "handler_defined_at": "src/web/api_handlers_storage_targets.c:562", + "ui_consumers": [ + "web/js/components/preact/settings/StorageTab.jsx:257" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 156, + "method": "GET", + "pattern": "/api/storage-targets/#", + "handler": "handle_get_storage_target", + "registered_at": "src/web/libuv_api_handlers.c:390", + "handler_defined_at": "src/web/api_handlers_storage_targets.c:472", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 157, + "method": "PUT", + "pattern": "/api/storage-targets/#", + "handler": "handle_put_storage_target", + "registered_at": "src/web/libuv_api_handlers.c:392", + "handler_defined_at": "src/web/api_handlers_storage_targets.c:487", + "ui_consumers": [ + "web/js/components/preact/settings/StorageTab.jsx:237" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 158, + "method": "DELETE", + "pattern": "/api/storage-targets/#", + "handler": "handle_delete_storage_target", + "registered_at": "src/web/libuv_api_handlers.c:394", + "handler_defined_at": "src/web/api_handlers_storage_targets.c:541", + "ui_consumers": [ + "web/js/components/preact/settings/StorageTab.jsx:277" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 159, + "method": "GET", + "pattern": "/api/storage-migrations", + "handler": "handle_get_storage_migrations", + "registered_at": "src/web/libuv_api_handlers.c:396", + "handler_defined_at": "src/web/api_handlers_storage_migrations.c:158", + "ui_consumers": [ + "web/js/components/preact/settings/StorageTab.jsx:444" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 160, + "method": "POST", + "pattern": "/api/storage-migrations", + "handler": "handle_post_storage_migration", + "registered_at": "src/web/libuv_api_handlers.c:398", + "handler_defined_at": "src/web/api_handlers_storage_migrations.c:196", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 161, + "method": "POST", + "pattern": "/api/storage-migrations/#/cancel", + "handler": "handle_post_storage_migration_cancel", + "registered_at": "src/web/libuv_api_handlers.c:400", + "handler_defined_at": "src/web/api_handlers_storage_migrations.c:308", + "ui_consumers": [ + "web/js/components/preact/settings/StorageTab.jsx:453" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 162, + "method": "POST", + "pattern": "/api/storage-migrations/#/retry", + "handler": "handle_post_storage_migration_retry", + "registered_at": "src/web/libuv_api_handlers.c:402", + "handler_defined_at": "src/web/api_handlers_storage_migrations.c:313", + "ui_consumers": [ + "web/js/components/preact/settings/StorageTab.jsx:453" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 163, + "method": "GET", + "pattern": "/api/storage-migrations/#", + "handler": "handle_get_storage_migration", + "registered_at": "src/web/libuv_api_handlers.c:404", + "handler_defined_at": "src/web/api_handlers_storage_migrations.c:265", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 164, + "method": "GET", + "pattern": "/api/storage-pools", + "handler": "handle_get_storage_pools", + "registered_at": "src/web/libuv_api_handlers.c:406", + "handler_defined_at": "src/web/api_handlers_storage_pools.c:224", + "ui_consumers": [ + "web/js/components/preact/settings/StorageTab.jsx:196", + "web/js/components/preact/settings/StorageTab.jsx:357", + "web/js/components/preact/settings/StorageTab.jsx:559" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 165, + "method": "POST", + "pattern": "/api/storage-pools", + "handler": "handle_post_storage_pool", + "registered_at": "src/web/libuv_api_handlers.c:408", + "handler_defined_at": "src/web/api_handlers_storage_pools.c:258", + "ui_consumers": [ + "web/js/components/preact/settings/StorageTab.jsx:373" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 166, + "method": "GET", + "pattern": "/api/storage-pools/#", + "handler": "handle_get_storage_pool", + "registered_at": "src/web/libuv_api_handlers.c:410", + "handler_defined_at": "src/web/api_handlers_storage_pools.c:284", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 167, + "method": "PUT", + "pattern": "/api/storage-pools/#", + "handler": "handle_put_storage_pool", + "registered_at": "src/web/libuv_api_handlers.c:412", + "handler_defined_at": "src/web/api_handlers_storage_pools.c:297", + "ui_consumers": [ + "web/js/components/preact/settings/StorageTab.jsx:373" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 168, + "method": "DELETE", + "pattern": "/api/storage-pools/#", + "handler": "handle_delete_storage_pool", + "registered_at": "src/web/libuv_api_handlers.c:414", + "handler_defined_at": "src/web/api_handlers_storage_pools.c:326", + "ui_consumers": [ + "web/js/components/preact/settings/StorageTab.jsx:382" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 169, + "method": "GET", + "pattern": "/api/storage-compliance", + "handler": "handle_get_storage_compliance", + "registered_at": "src/web/libuv_api_handlers.c:416", + "handler_defined_at": "src/web/api_handlers_storage_compliance.c:290", + "ui_consumers": [ + "web/js/components/preact/settings/StorageTab.jsx:672" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 170, + "method": "GET", + "pattern": "/api/storage-policies", + "handler": "handle_get_storage_policies", + "registered_at": "src/web/libuv_api_handlers.c:418", + "handler_defined_at": "src/web/api_handlers_storage_policies.c:660", + "ui_consumers": [ + "web/js/components/preact/settings/StorageTab.jsx:195", + "web/js/components/preact/settings/StorageTab.jsx:557" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 171, + "method": "POST", + "pattern": "/api/storage-policies", + "handler": "handle_post_storage_policy", + "registered_at": "src/web/libuv_api_handlers.c:420", + "handler_defined_at": "src/web/api_handlers_storage_policies.c:701", + "ui_consumers": [ + "web/js/components/preact/settings/StorageTab.jsx:630" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 172, + "method": "POST", + "pattern": "/api/storage-policies/preview", + "handler": "handle_post_storage_policy_preview", + "registered_at": "src/web/libuv_api_handlers.c:422", + "handler_defined_at": "src/web/api_handlers_storage_policies.c:422", + "ui_consumers": [ + "web/js/components/preact/settings/StorageTab.jsx:617" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 173, + "method": "GET", + "pattern": "/api/storage-policies/#", + "handler": "handle_get_storage_policy", + "registered_at": "src/web/libuv_api_handlers.c:424", + "handler_defined_at": "src/web/api_handlers_storage_policies.c:734", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 174, + "method": "PUT", + "pattern": "/api/storage-policies/#", + "handler": "handle_put_storage_policy", + "registered_at": "src/web/libuv_api_handlers.c:426", + "handler_defined_at": "src/web/api_handlers_storage_policies.c:749", + "ui_consumers": [ + "web/js/components/preact/settings/StorageTab.jsx:630" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 175, + "method": "DELETE", + "pattern": "/api/storage-policies/#", + "handler": "handle_delete_storage_policy", + "registered_at": "src/web/libuv_api_handlers.c:428", + "handler_defined_at": "src/web/api_handlers_storage_policies.c:801", + "ui_consumers": [ + "web/js/components/preact/settings/StorageTab.jsx:642" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 176, + "method": "POST", + "pattern": "/api/event-routes/preview", + "handler": "handle_post_event_route_preview", + "registered_at": "src/web/libuv_api_handlers.c:430", + "handler_defined_at": "src/web/api_handlers_event_routes.c:706", + "ui_consumers": [ + "web/js/components/preact/EventRoutingView.jsx:170" + ], + "integration_tests_referencing_path": [ + "tests/integration/specs/system-health-mqtt.api.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 177, + "method": "GET", + "pattern": "/api/event-routes", + "handler": "handle_get_event_routes", + "registered_at": "src/web/libuv_api_handlers.c:432", + "handler_defined_at": "src/web/api_handlers_event_routes.c:537", + "ui_consumers": [ + "web/js/components/preact/EventRoutingView.jsx:121" + ], + "integration_tests_referencing_path": [ + "tests/integration/specs/system-health-mqtt.api.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 178, + "method": "POST", + "pattern": "/api/event-routes", + "handler": "handle_post_event_route", + "registered_at": "src/web/libuv_api_handlers.c:434", + "handler_defined_at": "src/web/api_handlers_event_routes.c:584", + "ui_consumers": [ + "web/js/components/preact/EventRoutingView.jsx:164" + ], + "integration_tests_referencing_path": [ + "tests/integration/specs/system-health-mqtt.api.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 179, + "method": "GET", + "pattern": "/api/event-routes/#", + "handler": "handle_get_event_route", + "registered_at": "src/web/libuv_api_handlers.c:436", + "handler_defined_at": "src/web/api_handlers_event_routes.c:612", + "ui_consumers": [], + "integration_tests_referencing_path": [ + "tests/integration/specs/system-health-mqtt.api.spec.ts" + ], + "has_ui_consumer": false + }, + { + "order": 180, + "method": "PUT", + "pattern": "/api/event-routes/#", + "handler": "handle_put_event_route", + "registered_at": "src/web/libuv_api_handlers.c:438", + "handler_defined_at": "src/web/api_handlers_event_routes.c:627", + "ui_consumers": [ + "web/js/components/preact/EventRoutingView.jsx:164", + "web/js/components/preact/EventRoutingView.jsx:173" + ], + "integration_tests_referencing_path": [ + "tests/integration/specs/system-health-mqtt.api.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 181, + "method": "DELETE", + "pattern": "/api/event-routes/#", + "handler": "handle_delete_event_route", + "registered_at": "src/web/libuv_api_handlers.c:440", + "handler_defined_at": "src/web/api_handlers_event_routes.c:683", + "ui_consumers": [ + "web/js/components/preact/EventRoutingView.jsx:177" + ], + "integration_tests_referencing_path": [ + "tests/integration/specs/system-health-mqtt.api.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 182, + "method": "POST", + "pattern": "/api/auth/users/#/totp/setup", + "handler": "handle_totp_setup", + "registered_at": "src/web/libuv_api_handlers.c:444", + "handler_defined_at": "src/web/api_handlers_totp.c:280", + "ui_consumers": [ + "web/js/components/preact/users/TotpSetupModal.jsx:70" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 183, + "method": "POST", + "pattern": "/api/auth/users/#/totp/verify", + "handler": "handle_totp_verify", + "registered_at": "src/web/libuv_api_handlers.c:445", + "handler_defined_at": "src/web/api_handlers_totp.c:340", + "ui_consumers": [ + "web/js/components/preact/users/TotpSetupModal.jsx:96" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 184, + "method": "POST", + "pattern": "/api/auth/users/#/totp/disable", + "handler": "handle_totp_disable", + "registered_at": "src/web/libuv_api_handlers.c:446", + "handler_defined_at": "src/web/api_handlers_totp.c:408", + "ui_consumers": [ + "web/js/components/preact/users/TotpSetupModal.jsx:117" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 185, + "method": "GET", + "pattern": "/api/auth/users/#/totp/status", + "handler": "handle_totp_status", + "registered_at": "src/web/libuv_api_handlers.c:447", + "handler_defined_at": "src/web/api_handlers_totp.c:449", + "ui_consumers": [ + "web/js/components/preact/users/TotpSetupModal.jsx:33" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 186, + "method": "POST", + "pattern": "/api/auth/login/totp", + "handler": "handle_auth_login_totp", + "registered_at": "src/web/libuv_api_handlers.c:448", + "handler_defined_at": "src/web/api_handlers_totp.c:485", + "ui_consumers": [ + "web/js/components/preact/LoginView.jsx:246" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 187, + "method": "GET", + "pattern": "/api/onvif/discovery/status", + "handler": "handle_get_onvif_discovery_status", + "registered_at": "src/web/libuv_api_handlers.c:451", + "handler_defined_at": "src/web/api_handlers_onvif_backend_agnostic.c:60", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 188, + "method": "GET", + "pattern": "/api/onvif/devices", + "handler": "handle_get_discovered_onvif_devices", + "registered_at": "src/web/libuv_api_handlers.c:452", + "handler_defined_at": "src/web/api_handlers_onvif_backend_agnostic.c:99", + "ui_consumers": [ + "web/js/components/preact/StreamsView.jsx:992" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 189, + "method": "POST", + "pattern": "/api/onvif/discovery/discover", + "handler": "handle_post_discover_onvif_devices", + "registered_at": "src/web/libuv_api_handlers.c:453", + "handler_defined_at": "src/web/api_handlers_onvif_backend_agnostic.c:180", + "ui_consumers": [ + "web/js/components/preact/StreamsView.jsx:1085" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 190, + "method": "GET", + "pattern": "/api/onvif/device/profiles", + "handler": "handle_get_onvif_device_profiles", + "registered_at": "src/web/libuv_api_handlers.c:454", + "handler_defined_at": "src/web/api_handlers_onvif_backend_agnostic.c:290", + "ui_consumers": [ + "web/js/components/preact/StreamsView.jsx:1163" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 191, + "method": "POST", + "pattern": "/api/onvif/device/add", + "handler": "handle_post_add_onvif_device_as_stream", + "registered_at": "src/web/libuv_api_handlers.c:455", + "handler_defined_at": "src/web/api_handlers_onvif_backend_agnostic.c:409", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 192, + "method": "POST", + "pattern": "/api/onvif/device/claim", + "handler": "handle_post_claim_onvif_device", + "registered_at": "src/web/libuv_api_handlers.c:456", + "handler_defined_at": "src/web/api_handlers_onvif_backend_agnostic.c:524", + "ui_consumers": [ + "web/js/components/preact/StreamsView.jsx:1286" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 193, + "method": "POST", + "pattern": "/api/onvif/device/test", + "handler": "handle_post_test_onvif_connection", + "registered_at": "src/web/libuv_api_handlers.c:457", + "handler_defined_at": "src/web/api_handlers_onvif_backend_agnostic.c:564", + "ui_consumers": [ + "web/js/components/preact/StreamsView.jsx:1216" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 194, + "method": "GET", + "pattern": "/api/recordings/thumbnail/#/#", + "handler": "handle_recordings_thumbnail", + "registered_at": "src/web/libuv_api_handlers.c:461", + "handler_defined_at": "src/web/api_handlers_recordings_thumbnail.c:306", + "ui_consumers": [ + "web/js/components/preact/recordings/RecordingsGrid.jsx:131", + "web/js/components/preact/recordings/RecordingsGrid.jsx:144", + "web/js/components/preact/recordings/RecordingsGrid.jsx:185", + "web/js/components/preact/recordings/RecordingsGrid.jsx:222", + "web/js/components/preact/timeline/TimelineCursor.jsx:444", + "web/js/components/preact/timeline/TimelineCursor.jsx:73", + "web/js/components/preact/timeline/TimelineSegments.jsx:214", + "web/js/components/preact/timeline/TimelineSegments.jsx:352" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 195, + "method": "GET", + "pattern": "/api/recordings/play/#", + "handler": "handle_recordings_playback", + "registered_at": "src/web/libuv_api_handlers.c:462", + "handler_defined_at": "src/web/api_handlers_recordings_playback.c:36", + "ui_consumers": [ + "web/js/components/preact/RecordingsView.jsx:958", + "web/js/components/preact/investigation/InvestigationView.jsx:238", + "web/js/components/preact/recordings/recordingsAPI.jsx:690", + "web/js/components/preact/timeline/TimelinePlayer.jsx:291" + ], + "integration_tests_referencing_path": [ + "tests/integration/specs/recordings.ui.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 196, + "method": "GET", + "pattern": "/api/recordings/download/#", + "handler": "handle_recordings_download", + "registered_at": "src/web/libuv_api_handlers.c:463", + "handler_defined_at": "src/web/api_handlers_recordings_download.c:60", + "ui_consumers": [ + "web/js/components/preact/BatchDownloadModal.jsx:16", + "web/js/components/preact/RecordingsView.jsx:960", + "web/js/components/preact/recordings/recordingsAPI.jsx:692", + "web/js/components/preact/recordings/recordingsAPI.jsx:822", + "web/js/components/preact/timeline/TimelinePlayer.jsx:1075" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 197, + "method": "GET", + "pattern": "/api/recordings/files/check", + "handler": "handle_check_recording_file", + "registered_at": "src/web/libuv_api_handlers.c:464", + "handler_defined_at": "src/web/api_handlers_recordings_files_backend_agnostic.c:60", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 198, + "method": "DELETE", + "pattern": "/api/recordings/files", + "handler": "handle_delete_recording_file", + "registered_at": "src/web/libuv_api_handlers.c:465", + "handler_defined_at": "src/web/api_handlers_recordings_files_backend_agnostic.c:134", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 199, + "method": "GET", + "pattern": "/api/recordings/batch-delete/progress/#", + "handler": "handle_batch_delete_progress", + "registered_at": "src/web/libuv_api_handlers.c:466", + "handler_defined_at": "src/web/api_handlers_recordings_backend_agnostic.c:947", + "ui_consumers": [ + "web/js/components/preact/BatchDeleteModal.jsx:169", + "web/js/components/preact/recordings/recordingsAPI.jsx:472" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 200, + "method": "POST", + "pattern": "/api/recordings/batch-delete", + "handler": "handle_batch_delete_recordings", + "registered_at": "src/web/libuv_api_handlers.c:467", + "handler_defined_at": "src/web/api_handlers_recordings_backend_agnostic.c:802", + "ui_consumers": [ + "web/js/components/preact/BatchDeleteModal.jsx:134", + "web/js/components/preact/recordings/recordingsAPI.jsx:256", + "web/js/components/preact/recordings/recordingsAPI.jsx:435", + "web/js/components/preact/recordings/recordingsAPI.jsx:591" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 201, + "method": "GET", + "pattern": "/api/recordings/batch-download/status/#", + "handler": "handle_batch_download_status", + "registered_at": "src/web/libuv_api_handlers.c:468", + "handler_defined_at": "src/web/api_handlers_recordings_batch_download.c:644", + "ui_consumers": [ + "web/js/components/preact/BatchDownloadModal.jsx:50", + "web/js/components/preact/investigation/InvestigationActions.jsx:179" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 202, + "method": "GET", + "pattern": "/api/recordings/batch-download/result/#", + "handler": "handle_batch_download_result", + "registered_at": "src/web/libuv_api_handlers.c:469", + "handler_defined_at": "src/web/api_handlers_recordings_batch_download.c:683", + "ui_consumers": [ + "web/js/components/preact/BatchDownloadModal.jsx:60", + "web/js/components/preact/investigation/InvestigationActions.jsx:26" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 203, + "method": "POST", + "pattern": "/api/recordings/batch-download", + "handler": "handle_batch_download_recordings", + "registered_at": "src/web/libuv_api_handlers.c:470", + "handler_defined_at": "src/web/api_handlers_recordings_batch_download.c:487", + "ui_consumers": [ + "web/js/components/preact/BatchDownloadModal.jsx:39", + "web/js/components/preact/investigation/InvestigationActions.jsx:164" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 204, + "method": "GET", + "pattern": "/api/recordings/protected", + "handler": "handle_get_protected_recordings", + "registered_at": "src/web/libuv_api_handlers.c:471", + "handler_defined_at": "src/web/api_handlers_retention.c:397", + "ui_consumers": [], + "integration_tests_referencing_path": [ + "tests/integration/specs/comprehensive.api.spec.ts" + ], + "has_ui_consumer": false + }, + { + "order": 205, + "method": "POST", + "pattern": "/api/recordings/batch-protect", + "handler": "handle_batch_protect_recordings", + "registered_at": "src/web/libuv_api_handlers.c:472", + "handler_defined_at": "src/web/api_handlers_retention.c:460", + "ui_consumers": [ + "web/js/components/preact/investigation/InvestigationActions.jsx:129" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 206, + "method": "POST", + "pattern": "/api/recordings/sync", + "handler": "handle_post_recordings_sync", + "registered_at": "src/web/libuv_api_handlers.c:473", + "handler_defined_at": "src/web/api_handlers_recordings_sync.c:32", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 207, + "method": "GET", + "pattern": "/api/recordings/tags", + "handler": "handle_get_recording_tags", + "registered_at": "src/web/libuv_api_handlers.c:474", + "handler_defined_at": "src/web/api_handlers_recording_tags.c:82", + "ui_consumers": [ + "web/js/components/preact/recordings/recordingsAPI.jsx:715" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 208, + "method": "GET", + "pattern": "/api/recordings/detection-labels", + "handler": "handle_get_recording_detection_labels", + "registered_at": "src/web/libuv_api_handlers.c:475", + "handler_defined_at": "src/web/api_handlers_recording_tags.c:116", + "ui_consumers": [ + "web/js/components/preact/recordings/recordingsAPI.jsx:737" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 209, + "method": "POST", + "pattern": "/api/recordings/batch-tags", + "handler": "handle_batch_recording_tags", + "registered_at": "src/web/libuv_api_handlers.c:476", + "handler_defined_at": "src/web/api_handlers_recording_tags.c:307", + "ui_consumers": [ + "web/js/components/preact/recordings/recordingsAPI.jsx:801" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 210, + "method": "GET", + "pattern": "/api/recordings/#/tags", + "handler": "handle_get_recording_tags_by_id", + "registered_at": "src/web/libuv_api_handlers.c:477", + "handler_defined_at": "src/web/api_handlers_recording_tags.c:150", + "ui_consumers": [ + "web/js/components/preact/recordings/recordingsAPI.jsx:756", + "web/js/components/preact/timeline/TimelinePlayer.jsx:612" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 211, + "method": "PUT", + "pattern": "/api/recordings/#/tags", + "handler": "handle_put_recording_tags", + "registered_at": "src/web/libuv_api_handlers.c:478", + "handler_defined_at": "src/web/api_handlers_recording_tags.c:200", + "ui_consumers": [ + "web/js/components/preact/recordings/recordingsAPI.jsx:776" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 212, + "method": "PUT", + "pattern": "/api/recordings/#/protect", + "handler": "handle_put_recording_protect", + "registered_at": "src/web/libuv_api_handlers.c:479", + "handler_defined_at": "src/web/api_handlers_retention.c:227", + "ui_consumers": [ + "web/js/components/preact/RecordingsView.jsx:908", + "web/js/components/preact/UI.jsx:367", + "web/js/components/preact/timeline/TimelineControls.jsx:245" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 213, + "method": "PUT", + "pattern": "/api/recordings/#/retention", + "handler": "handle_put_recording_retention", + "registered_at": "src/web/libuv_api_handlers.c:480", + "handler_defined_at": "src/web/api_handlers_retention.c:309", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 214, + "method": "GET", + "pattern": "/api/recordings/#", + "handler": "handle_get_recording", + "registered_at": "src/web/libuv_api_handlers.c:481", + "handler_defined_at": "src/web/api_handlers_recordings_backend_agnostic.c:69", + "ui_consumers": [ + "web/js/components/preact/UI.jsx:292", + "web/js/components/preact/recordings/recordingsAPI.jsx:166", + "web/js/components/preact/timeline/TimelinePlayer.jsx:575" + ], + "integration_tests_referencing_path": [ + "tests/integration/specs/comprehensive.api.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 215, + "method": "DELETE", + "pattern": "/api/recordings/#", + "handler": "handle_delete_recording", + "registered_at": "src/web/libuv_api_handlers.c:482", + "handler_defined_at": "src/web/api_handlers_recordings_backend_agnostic.c:257", + "ui_consumers": [ + "web/js/components/preact/UI.jsx:393", + "web/js/components/preact/recordings/recordingsAPI.jsx:227", + "web/js/components/preact/recordings/recordingsAPI.jsx:382", + "web/js/components/preact/timeline/TimelinePlayer.jsx:847" + ], + "integration_tests_referencing_path": [ + "tests/integration/specs/comprehensive.api.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 216, + "method": "GET", + "pattern": "/api/recordings", + "handler": "handle_get_recordings", + "registered_at": "src/web/libuv_api_handlers.c:483", + "handler_defined_at": "src/web/api_handlers_recordings_list.c:312", + "ui_consumers": [ + "web/js/components/preact/recordings/recordingsAPI.jsx:204", + "web/js/components/preact/recordings/recordingsAPI.jsx:347", + "web/js/components/preact/recordings/recordingsAPI.jsx:546" + ], + "integration_tests_referencing_path": [ + "tests/integration/fixtures/test-fixtures.ts", + "tests/integration/lightnvr.api.spec.ts", + "tests/integration/specs/comprehensive.api.spec.ts" + ], + "has_ui_consumer": true + }, + { + "order": 217, + "method": "GET", + "pattern": "/api/timeline/segments-by-ids", + "handler": "handle_get_timeline_segments_by_ids", + "registered_at": "src/web/libuv_api_handlers.c:486", + "handler_defined_at": "src/web/api_handlers_timeline.c:1021", + "ui_consumers": [ + "web/js/components/preact/timeline/TimelinePage.jsx:1094" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 218, + "method": "GET", + "pattern": "/api/timeline/segments", + "handler": "handle_get_timeline_segments", + "registered_at": "src/web/libuv_api_handlers.c:487", + "handler_defined_at": "src/web/api_handlers_timeline.c:459", + "ui_consumers": [ + "web/js/components/preact/timeline/timelineUtils.js:22" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 219, + "method": "GET", + "pattern": "/api/timeline/manifest", + "handler": "handle_timeline_manifest", + "registered_at": "src/web/libuv_api_handlers.c:488", + "handler_defined_at": "src/web/api_handlers_timeline.c:723", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 220, + "method": "GET", + "pattern": "/api/timeline/play", + "handler": "handle_timeline_playback", + "registered_at": "src/web/libuv_api_handlers.c:489", + "handler_defined_at": "src/web/api_handlers_timeline.c:811", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 221, + "method": "POST", + "pattern": "/api/investigations/timeline", + "handler": "handle_post_investigation_timeline", + "registered_at": "src/web/libuv_api_handlers.c:490", + "handler_defined_at": "src/web/api_handlers_investigations.c:251", + "ui_consumers": [ + "web/js/components/preact/investigation/InvestigationView.jsx:987" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 222, + "method": "POST", + "pattern": "/api/investigations/segment-at", + "handler": "handle_post_investigation_segment_at", + "registered_at": "src/web/libuv_api_handlers.c:491", + "handler_defined_at": "src/web/api_handlers_investigations.c:426", + "ui_consumers": [ + "web/js/components/preact/investigation/InvestigationView.jsx:150" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 223, + "method": "POST", + "pattern": "/api/investigations/search", + "handler": "handle_post_investigation_search", + "registered_at": "src/web/libuv_api_handlers.c:492", + "handler_defined_at": "src/web/api_handlers_investigations.c:1121", + "ui_consumers": [ + "web/js/components/preact/investigation/InvestigationView.jsx:878", + "web/js/components/preact/investigation/InvestigationView.jsx:897", + "web/js/components/preact/live/LiveOperatorNavigator.jsx:149" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 224, + "method": "POST", + "pattern": "/api/investigations/recordings/preview", + "handler": "handle_post_investigation_recording_preview", + "registered_at": "src/web/libuv_api_handlers.c:493", + "handler_defined_at": "src/web/api_handlers_investigations.c:488", + "ui_consumers": [ + "web/js/components/preact/investigation/InvestigationActions.jsx:66" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 225, + "method": "POST", + "pattern": "/api/investigations/thumbnail-samples", + "handler": "handle_post_investigation_thumbnail_samples", + "registered_at": "src/web/libuv_api_handlers.c:494", + "handler_defined_at": "src/web/api_handlers_investigations.c:675", + "ui_consumers": [ + "web/js/components/preact/investigation/InvestigationView.jsx:1262" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 226, + "method": "GET", + "pattern": "/api/investigations/thumbnail/#/#", + "handler": "handle_investigation_thumbnail", + "registered_at": "src/web/libuv_api_handlers.c:495", + "handler_defined_at": "src/web/api_handlers_recordings_thumbnail.c:401", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 227, + "method": "GET", + "pattern": "/api/investigation-bookmarks/#", + "handler": "handle_get_investigation_bookmark", + "registered_at": "src/web/libuv_api_handlers.c:496", + "handler_defined_at": "src/web/api_handlers_investigation_bookmarks.c:578", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 228, + "method": "PUT", + "pattern": "/api/investigation-bookmarks/#", + "handler": "handle_put_investigation_bookmark", + "registered_at": "src/web/libuv_api_handlers.c:497", + "handler_defined_at": "src/web/api_handlers_investigation_bookmarks.c:602", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 229, + "method": "DELETE", + "pattern": "/api/investigation-bookmarks/#", + "handler": "handle_delete_investigation_bookmark", + "registered_at": "src/web/libuv_api_handlers.c:498", + "handler_defined_at": "src/web/api_handlers_investigation_bookmarks.c:648", + "ui_consumers": [ + "web/js/components/preact/investigation/InvestigationBookmarks.jsx:103" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 230, + "method": "GET", + "pattern": "/api/investigation-bookmarks", + "handler": "handle_get_investigation_bookmarks", + "registered_at": "src/web/libuv_api_handlers.c:499", + "handler_defined_at": "src/web/api_handlers_investigation_bookmarks.c:465", + "ui_consumers": [ + "web/js/components/preact/investigation/InvestigationBookmarks.jsx:50" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 231, + "method": "POST", + "pattern": "/api/investigation-bookmarks", + "handler": "handle_post_investigation_bookmark", + "registered_at": "src/web/libuv_api_handlers.c:500", + "handler_defined_at": "src/web/api_handlers_investigation_bookmarks.c:525", + "ui_consumers": [ + "web/js/components/preact/investigation/InvestigationBookmarks.jsx:80" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 232, + "method": "GET", + "pattern": "/hls/#/#", + "handler": "handle_direct_hls_request", + "registered_at": "src/web/libuv_api_handlers.c:504", + "handler_defined_at": "src/web/api_handlers_streaming.c:28", + "ui_consumers": [ + "web/js/components/preact/HLSVideoCell.jsx:206", + "web/js/components/preact/HLSVideoCell.jsx:260", + "web/js/components/preact/HLSVideoCell.jsx:266" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 233, + "method": "GET", + "pattern": "/go2rtc/api/ws", + "handler": "handle_go2rtc_proxy", + "registered_at": "src/web/libuv_api_handlers.c:508", + "handler_defined_at": "src/web/api_handlers_go2rtc_proxy.c:504", + "ui_consumers": [ + "web/js/components/preact/MSEVideoCell.jsx:220" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 234, + "method": "POST", + "pattern": "/go2rtc/api/webrtc", + "handler": "handle_go2rtc_proxy", + "registered_at": "src/web/libuv_api_handlers.c:509", + "handler_defined_at": "src/web/api_handlers_go2rtc_proxy.c:504", + "ui_consumers": [ + "web/js/components/preact/WebRTCVideoCell.jsx:852" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 235, + "method": "GET", + "pattern": "/go2rtc/api/streams", + "handler": "handle_go2rtc_proxy", + "registered_at": "src/web/libuv_api_handlers.c:511", + "handler_defined_at": "src/web/api_handlers_go2rtc_proxy.c:504", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 236, + "method": "POST", + "pattern": "/go2rtc/api/reload", + "handler": "handle_go2rtc_proxy", + "registered_at": "src/web/libuv_api_handlers.c:513", + "handler_defined_at": "src/web/api_handlers_go2rtc_proxy.c:504", + "ui_consumers": [], + "integration_tests_referencing_path": [], + "has_ui_consumer": false + }, + { + "order": 237, + "method": "GET", + "pattern": "/go2rtc/api/stream.m3u8", + "handler": "handle_go2rtc_proxy", + "registered_at": "src/web/libuv_api_handlers.c:515", + "handler_defined_at": "src/web/api_handlers_go2rtc_proxy.c:504", + "ui_consumers": [ + "web/js/components/preact/HLSVideoCell.jsx:245" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 238, + "method": "GET", + "pattern": "/go2rtc/api/hls/*", + "handler": "handle_go2rtc_proxy", + "registered_at": "src/web/libuv_api_handlers.c:517", + "handler_defined_at": "src/web/api_handlers_go2rtc_proxy.c:504", + "ui_consumers": [ + "web/js/components/preact/HLSVideoCell.jsx:245" + ], + "integration_tests_referencing_path": [], + "has_ui_consumer": true + }, + { + "order": 239, + "method": "GET", + "pattern": "/go2rtc/api/frame.jpeg", + "handler": "handle_go2rtc_proxy", + "registered_at": "src/web/libuv_api_handlers.c:519", + "handler_defined_at": "src/web/api_handlers_go2rtc_proxy.c:504", + "ui_consumers": [ + "web/js/components/preact/StreamCard.jsx:140", + "web/js/components/preact/ZoneEditor.jsx:82", + "web/js/components/preact/ZoneEditor.jsx:88", + "web/js/components/preact/live/LiveBuildingPlan.jsx:90" + ], + "integration_tests_referencing_path": [ + "tests/integration/specs/stream-snapshot-requests.ui.spec.ts" + ], + "has_ui_consumer": true + } + ], + "ui_operations": [ + { + "file": "web/js/components/preact/AuthGate.jsx", + "line": 38, + "url_literal": "/api/auth/users/${session.id}/password", + "normalized": "/api/auth/users/{}/password", + "method": "PUT", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "PUT /api/auth/users/#/password", + "matched_handler": "handle_users_change_password", + "domain": "shared-auth", + "status": "mapped", + "id": "UI-001" + }, + { + "file": "web/js/components/preact/BatchDeleteModal.jsx", + "line": 134, + "url_literal": "/api/recordings/batch-delete", + "normalized": "/api/recordings/batch-delete", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/recordings/batch-delete", + "matched_handler": "handle_batch_delete_recordings", + "domain": "recordings", + "status": "mapped", + "id": "UI-002" + }, + { + "file": "web/js/components/preact/BatchDeleteModal.jsx", + "line": 169, + "url_literal": "/api/recordings/batch-delete/progress/${jobId}", + "normalized": "/api/recordings/batch-delete/progress/{}", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/recordings/batch-delete/progress/#", + "matched_handler": "handle_batch_delete_progress", + "domain": "recordings", + "status": "mapped", + "id": "UI-003" + }, + { + "file": "web/js/components/preact/BatchDownloadModal.jsx", + "line": 16, + "url_literal": "/api/recordings/download/${r.id}", + "normalized": "/api/recordings/download/{}", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "media-or-binary", + "matched_route": "GET /api/recordings/download/#", + "matched_handler": "handle_recordings_download", + "domain": "recordings", + "status": "mapped", + "id": "UI-004" + }, + { + "file": "web/js/components/preact/BatchDownloadModal.jsx", + "line": 39, + "url_literal": "/api/recordings/batch-download", + "normalized": "/api/recordings/batch-download", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/recordings/batch-download", + "matched_handler": "handle_batch_download_recordings", + "domain": "recordings", + "status": "mapped", + "id": "UI-005" + }, + { + "file": "web/js/components/preact/BatchDownloadModal.jsx", + "line": 50, + "url_literal": "/api/recordings/batch-download/status/${token}", + "normalized": "/api/recordings/batch-download/status/{}", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/recordings/batch-download/status/#", + "matched_handler": "handle_batch_download_status", + "domain": "recordings", + "status": "mapped", + "id": "UI-006" + }, + { + "file": "web/js/components/preact/BatchDownloadModal.jsx", + "line": 60, + "url_literal": "/api/recordings/batch-download/result/${token}", + "normalized": "/api/recordings/batch-download/result/{}", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/recordings/batch-download/result/#", + "matched_handler": "handle_batch_download_result", + "domain": "recordings", + "status": "mapped", + "id": "UI-007" + }, + { + "file": "web/js/components/preact/DayNightControl.jsx", + "line": 69, + "url_literal": "/api/streams/${encodeURIComponent(stream.name)}/daynight", + "normalized": "/api/streams/{}/daynight", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/streams/#/daynight", + "matched_handler": "handle_daynight_get", + "domain": "live", + "status": "mapped", + "id": "UI-008" + }, + { + "file": "web/js/components/preact/DayNightControl.jsx", + "line": 95, + "url_literal": "/api/streams/${encodeURIComponent(stream.name)}/daynight", + "normalized": "/api/streams/{}/daynight", + "method": "PUT", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "PUT /api/streams/#/daynight", + "matched_handler": "handle_daynight_put", + "domain": "live", + "status": "mapped", + "id": "UI-009" + }, + { + "file": "web/js/components/preact/DetectionOverlay.jsx", + "line": 43, + "url_literal": "/api/streams/${encodeURIComponent(streamName)}/zones", + "normalized": "/api/streams/{}/zones", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/streams/#/zones", + "matched_handler": "handle_get_zones", + "domain": "live", + "status": "mapped", + "id": "UI-010" + }, + { + "file": "web/js/components/preact/EventRoutingView.jsx", + "line": 121, + "url_literal": "/api/event-routes", + "normalized": "/api/event-routes", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/event-routes", + "matched_handler": "handle_get_event_routes", + "domain": "events", + "status": "mapped", + "id": "UI-011" + }, + { + "file": "web/js/components/preact/EventRoutingView.jsx", + "line": 122, + "url_literal": "/api/event-destinations", + "normalized": "/api/event-destinations", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/event-destinations", + "matched_handler": "handle_get_event_destinations", + "domain": "events", + "status": "mapped", + "id": "UI-012" + }, + { + "file": "web/js/components/preact/EventRoutingView.jsx", + "line": 123, + "url_literal": "/api/events/catalog", + "normalized": "/api/events/catalog", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/events/catalog", + "matched_handler": "handle_get_event_catalog", + "domain": "events", + "status": "mapped", + "id": "UI-013" + }, + { + "file": "web/js/components/preact/EventRoutingView.jsx", + "line": 124, + "url_literal": "/api/settings", + "normalized": "/api/settings", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/settings", + "matched_handler": "handle_get_settings", + "domain": "events", + "status": "mapped", + "id": "UI-014" + }, + { + "file": "web/js/components/preact/EventRoutingView.jsx", + "line": 125, + "url_literal": "/api/locations", + "normalized": "/api/locations", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/locations", + "matched_handler": "handle_get_locations", + "domain": "events", + "status": "mapped", + "id": "UI-015" + }, + { + "file": "web/js/components/preact/EventRoutingView.jsx", + "line": 126, + "url_literal": "/api/camera-tags", + "normalized": "/api/camera-tags", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/camera-tags", + "matched_handler": "handle_get_camera_tags", + "domain": "events", + "status": "mapped", + "id": "UI-016" + }, + { + "file": "web/js/components/preact/EventRoutingView.jsx", + "line": 164, + "url_literal": "/api/event-routes/${encodeURIComponent(routeEditor.uuid)}", + "normalized": "/api/event-routes/{}", + "method": "PUT", + "method_evidence": "hand-reviewed ternary: method method: editing ? 'PUT' : 'POST' (URL #1 on line)", + "transport": "json", + "matched_route": "PUT /api/event-routes/#", + "matched_handler": "handle_put_event_route", + "domain": "events", + "status": "mapped", + "id": "UI-017" + }, + { + "file": "web/js/components/preact/EventRoutingView.jsx", + "line": 164, + "url_literal": "/api/event-routes", + "normalized": "/api/event-routes", + "method": "POST", + "method_evidence": "hand-reviewed ternary: method method: editing ? 'PUT' : 'POST' (URL #2 on line)", + "transport": "json", + "matched_route": "POST /api/event-routes", + "matched_handler": "handle_post_event_route", + "domain": "events", + "status": "mapped", + "id": "UI-018" + }, + { + "file": "web/js/components/preact/EventRoutingView.jsx", + "line": 170, + "url_literal": "/api/event-routes/preview", + "normalized": "/api/event-routes/preview", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/event-routes/preview", + "matched_handler": "handle_post_event_route_preview", + "domain": "events", + "status": "mapped", + "id": "UI-019" + }, + { + "file": "web/js/components/preact/EventRoutingView.jsx", + "line": 173, + "url_literal": "/api/event-routes/${encodeURIComponent(route.uuid)}", + "normalized": "/api/event-routes/{}", + "method": "PUT", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "PUT /api/event-routes/#", + "matched_handler": "handle_put_event_route", + "domain": "events", + "status": "mapped", + "id": "UI-020" + }, + { + "file": "web/js/components/preact/EventRoutingView.jsx", + "line": 177, + "url_literal": "/api/event-routes/${encodeURIComponent(route.uuid)}?revision=${encodeURIComponent(route.revision)}", + "normalized": "/api/event-routes/{}", + "method": "DELETE", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "DELETE /api/event-routes/#", + "matched_handler": "handle_delete_event_route", + "domain": "events", + "status": "mapped", + "id": "UI-021" + }, + { + "file": "web/js/components/preact/EventRoutingView.jsx", + "line": 182, + "url_literal": "/api/event-destinations/${encodeURIComponent(destinationEditor.uuid)}", + "normalized": "/api/event-destinations/{}", + "method": "PUT", + "method_evidence": "hand-reviewed ternary: method method: editing ? 'PUT' : 'POST' (URL #1 on line)", + "transport": "json", + "matched_route": "PUT /api/event-destinations/#", + "matched_handler": "handle_put_event_destination", + "domain": "events", + "status": "mapped", + "id": "UI-022" + }, + { + "file": "web/js/components/preact/EventRoutingView.jsx", + "line": 182, + "url_literal": "/api/event-destinations", + "normalized": "/api/event-destinations", + "method": "POST", + "method_evidence": "hand-reviewed ternary: method method: editing ? 'PUT' : 'POST' (URL #2 on line)", + "transport": "json", + "matched_route": "POST /api/event-destinations", + "matched_handler": "handle_post_event_destination", + "domain": "events", + "status": "mapped", + "id": "UI-023" + }, + { + "file": "web/js/components/preact/EventRoutingView.jsx", + "line": 190, + "url_literal": "/api/event-destinations/${encodeURIComponent(destination.uuid)}", + "normalized": "/api/event-destinations/{}", + "method": "PUT", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "PUT /api/event-destinations/#", + "matched_handler": "handle_put_event_destination", + "domain": "events", + "status": "mapped", + "id": "UI-024" + }, + { + "file": "web/js/components/preact/EventRoutingView.jsx", + "line": 194, + "url_literal": "/api/event-destinations/${encodeURIComponent(destination.uuid)}?revision=${encodeURIComponent(destination.revision)}", + "normalized": "/api/event-destinations/{}", + "method": "DELETE", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "DELETE /api/event-destinations/#", + "matched_handler": "handle_delete_event_destination", + "domain": "events", + "status": "mapped", + "id": "UI-025" + }, + { + "file": "web/js/components/preact/FleetView.jsx", + "line": 134, + "url_literal": "/api/fleet/cameras/query", + "normalized": "/api/fleet/cameras/query", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/fleet/cameras/query", + "matched_handler": "handle_post_fleet_camera_query", + "domain": "fleet", + "status": "mapped", + "id": "UI-026" + }, + { + "file": "web/js/components/preact/FleetView.jsx", + "line": 151, + "url_literal": "/api/locations", + "normalized": "/api/locations", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/locations", + "matched_handler": "handle_get_locations", + "domain": "fleet", + "status": "mapped", + "id": "UI-027" + }, + { + "file": "web/js/components/preact/FleetView.jsx", + "line": 159, + "url_literal": "/api/camera-tags", + "normalized": "/api/camera-tags", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/camera-tags", + "matched_handler": "handle_get_camera_tags", + "domain": "fleet", + "status": "mapped", + "id": "UI-028" + }, + { + "file": "web/js/components/preact/FleetView.jsx", + "line": 167, + "url_literal": "/api/camera-collections", + "normalized": "/api/camera-collections", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/camera-collections", + "matched_handler": "handle_get_camera_collections", + "domain": "fleet", + "status": "mapped", + "id": "UI-029" + }, + { + "file": "web/js/components/preact/FleetView.jsx", + "line": 173, + "url_literal": "/api/fleet/views", + "normalized": "/api/fleet/views", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/fleet/views", + "matched_handler": "handle_get_fleet_saved_views", + "domain": "fleet", + "status": "mapped", + "id": "UI-030" + }, + { + "file": "web/js/components/preact/HLSVideoCell.jsx", + "line": 153, + "url_literal": "/api/streams/${encodeURIComponent(stream.name)}/refresh", + "normalized": "/api/streams/{}/refresh", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/streams/#/refresh", + "matched_handler": "handle_post_stream_refresh", + "domain": "live", + "status": "mapped", + "id": "UI-031" + }, + { + "file": "web/js/components/preact/HLSVideoCell.jsx", + "line": 206, + "url_literal": "/hls/${encodeURIComponent(stream.name)}/index.m3u8", + "normalized": "/hls/{}/index.m3u8", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "native-hls-media", + "matched_route": "GET /hls/#/#", + "matched_handler": "handle_direct_hls_request", + "domain": "live", + "status": "mapped", + "id": "UI-032" + }, + { + "file": "web/js/components/preact/HLSVideoCell.jsx", + "line": 245, + "url_literal": "${go2rtcBaseUrl}/api/stream.m3u8?src=${encodeURIComponent(effectiveName)}&mp4=flac${audioDisabled ? '&video' : ''}", + "normalized": "/go2rtc/api/stream.m3u8", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "go2rtc-proxy", + "matched_route": "GET /go2rtc/api/stream.m3u8", + "matched_handler": "handle_go2rtc_proxy", + "domain": "live", + "status": "mapped", + "id": "UI-033" + }, + { + "file": "web/js/components/preact/HLSVideoCell.jsx", + "line": 260, + "url_literal": "/hls/${encodeURIComponent(stream.name)}/index.m3u8", + "normalized": "/hls/{}/index.m3u8", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "native-hls-media", + "matched_route": "GET /hls/#/#", + "matched_handler": "handle_direct_hls_request", + "domain": "live", + "status": "mapped", + "id": "UI-034" + }, + { + "file": "web/js/components/preact/HLSVideoCell.jsx", + "line": 266, + "url_literal": "/hls/${encodeURIComponent(stream.name)}/index.m3u8", + "normalized": "/hls/{}/index.m3u8", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "native-hls-media", + "matched_route": "GET /hls/#/#", + "matched_handler": "handle_direct_hls_request", + "domain": "live", + "status": "mapped", + "id": "UI-035" + }, + { + "file": "web/js/components/preact/HLSVideoCell.jsx", + "line": 665, + "url_literal": "/api/streams/${encodeURIComponent(stream.name)}", + "normalized": "/api/streams/{}", + "method": "PUT", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "PUT /api/streams/#", + "matched_handler": "handle_put_stream", + "domain": "live", + "status": "mapped", + "id": "UI-036" + }, + { + "file": "web/js/components/preact/HLSVideoCell.jsx", + "line": 688, + "url_literal": "/api/streams/${encodeURIComponent(stream.name)}", + "normalized": "/api/streams/{}", + "method": "PUT", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "PUT /api/streams/#", + "matched_handler": "handle_put_stream", + "domain": "live", + "status": "mapped", + "id": "UI-037" + }, + { + "file": "web/js/components/preact/Header.jsx", + "line": 177, + "url_literal": "/api/auth/users/${currentUser.id}", + "normalized": "/api/auth/users/{}", + "method": "PUT", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "PUT /api/auth/users/#", + "matched_handler": "handle_users_update", + "domain": "shared-shell", + "status": "mapped", + "id": "UI-038" + }, + { + "file": "web/js/components/preact/Header.jsx", + "line": 306, + "url_literal": "/logout", + "normalized": "/logout", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "navigation", + "matched_route": "GET /logout", + "matched_handler": "handle_auth_logout", + "domain": "shared-shell", + "status": "mapped", + "id": "UI-039" + }, + { + "file": "web/js/components/preact/HealthView.jsx", + "line": 156, + "url_literal": "/api/health", + "normalized": "/api/health", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/health", + "matched_handler": "handle_get_health", + "domain": "system-health", + "status": "mapped", + "id": "UI-040" + }, + { + "file": "web/js/components/preact/HealthView.jsx", + "line": 162, + "url_literal": "/api/health?sparklines=true&history_only=true", + "normalized": "/api/health", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/health", + "matched_handler": "handle_get_health", + "domain": "system-health", + "status": "mapped", + "id": "UI-041" + }, + { + "file": "web/js/components/preact/LoginView.jsx", + "line": 71, + "url_literal": "/api/auth/logout", + "normalized": "/api/auth/logout", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/auth/logout", + "matched_handler": "handle_auth_logout", + "domain": "shared-auth", + "status": "mapped", + "id": "UI-042" + }, + { + "file": "web/js/components/preact/LoginView.jsx", + "line": 80, + "url_literal": "/api/auth/login/config", + "normalized": "/api/auth/login/config", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/auth/login/config", + "matched_handler": "handle_auth_login_config", + "domain": "shared-auth", + "status": "mapped", + "id": "UI-043" + }, + { + "file": "web/js/components/preact/LoginView.jsx", + "line": 178, + "url_literal": "/api/auth/login", + "normalized": "/api/auth/login", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/auth/login", + "matched_handler": "handle_auth_login", + "domain": "shared-auth", + "status": "mapped", + "id": "UI-044" + }, + { + "file": "web/js/components/preact/LoginView.jsx", + "line": 246, + "url_literal": "/api/auth/login/totp", + "normalized": "/api/auth/login/totp", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/auth/login/totp", + "matched_handler": "handle_auth_login_totp", + "domain": "shared-auth", + "status": "mapped", + "id": "UI-045" + }, + { + "file": "web/js/components/preact/MSEVideoCell.jsx", + "line": 220, + "url_literal": "${go2rtcWsUrl}/api/ws?src=${encodeURIComponent(effectiveName)}", + "normalized": "/go2rtc/api/ws", + "method": "GET", + "method_evidence": "websocket-upgrade", + "transport": "go2rtc-proxy-websocket", + "matched_route": "GET /go2rtc/api/ws", + "matched_handler": "handle_go2rtc_proxy", + "domain": "live", + "status": "mapped", + "id": "UI-046" + }, + { + "file": "web/js/components/preact/MSEVideoCell.jsx", + "line": 553, + "url_literal": "/api/streams/${encodeURIComponent(stream.name)}", + "normalized": "/api/streams/{}", + "method": "PUT", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "PUT /api/streams/#", + "matched_handler": "handle_put_stream", + "domain": "live", + "status": "mapped", + "id": "UI-047" + }, + { + "file": "web/js/components/preact/MSEVideoCell.jsx", + "line": 576, + "url_literal": "/api/streams/${encodeURIComponent(stream.name)}", + "normalized": "/api/streams/{}", + "method": "PUT", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "PUT /api/streams/#", + "matched_handler": "handle_put_stream", + "domain": "live", + "status": "mapped", + "id": "UI-048" + }, + { + "file": "web/js/components/preact/ManualRecordingButton.jsx", + "line": 10, + "url_literal": "/api/streams/${encodeURIComponent(streamName)}/recording", + "normalized": "/api/streams/{}/recording", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/streams/#/recording", + "matched_handler": "handle_get_stream_recording", + "domain": "live", + "status": "mapped", + "id": "UI-049" + }, + { + "file": "web/js/components/preact/PTZControls.jsx", + "line": 17, + "url_literal": "/api/streams/${encodeURIComponent(streamName)}/ptz/move", + "normalized": "/api/streams/{}/ptz/move", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/streams/#/ptz/move", + "matched_handler": "handle_ptz_move", + "domain": "live", + "status": "mapped", + "id": "UI-050" + }, + { + "file": "web/js/components/preact/PTZControls.jsx", + "line": 26, + "url_literal": "/api/streams/${encodeURIComponent(streamName)}/ptz/stop", + "normalized": "/api/streams/{}/ptz/stop", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/streams/#/ptz/stop", + "matched_handler": "handle_ptz_stop", + "domain": "live", + "status": "mapped", + "id": "UI-051" + }, + { + "file": "web/js/components/preact/PTZControls.jsx", + "line": 33, + "url_literal": "/api/streams/${encodeURIComponent(streamName)}/ptz/home", + "normalized": "/api/streams/{}/ptz/home", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/streams/#/ptz/home", + "matched_handler": "handle_ptz_home", + "domain": "live", + "status": "mapped", + "id": "UI-052" + }, + { + "file": "web/js/components/preact/PTZControls.jsx", + "line": 40, + "url_literal": "/api/streams/${encodeURIComponent(streamName)}/ptz/goto-preset", + "normalized": "/api/streams/{}/ptz/goto-preset", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/streams/#/ptz/goto-preset", + "matched_handler": "handle_ptz_goto_preset", + "domain": "live", + "status": "mapped", + "id": "UI-053" + }, + { + "file": "web/js/components/preact/RecordingsView.jsx", + "line": 82, + "url_literal": "/api/camera-collections", + "normalized": "/api/camera-collections", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/camera-collections", + "matched_handler": "handle_get_camera_collections", + "domain": "recordings", + "status": "mapped", + "id": "UI-054" + }, + { + "file": "web/js/components/preact/RecordingsView.jsx", + "line": 184, + "url_literal": "/api/client-config", + "normalized": "/api/client-config", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/client-config", + "matched_handler": "handle_get_client_config", + "domain": "recordings", + "status": "mapped", + "id": "UI-055" + }, + { + "file": "web/js/components/preact/RecordingsView.jsx", + "line": 908, + "url_literal": "/api/recordings/${recording.id}/protect", + "normalized": "/api/recordings/{}/protect", + "method": "PUT", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "PUT /api/recordings/#/protect", + "matched_handler": "handle_put_recording_protect", + "domain": "recordings", + "status": "mapped", + "id": "UI-056" + }, + { + "file": "web/js/components/preact/RecordingsView.jsx", + "line": 958, + "url_literal": "/api/recordings/play/${recording.id}${recording.external_source ? \"?archive=1\" : \"\"}", + "normalized": "/api/recordings/play/{}", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "media-or-binary", + "matched_route": "GET /api/recordings/play/#", + "matched_handler": "handle_recordings_playback", + "domain": "recordings", + "status": "mapped", + "id": "UI-057" + }, + { + "file": "web/js/components/preact/RecordingsView.jsx", + "line": 960, + "url_literal": "/api/recordings/download/${recording.id}", + "normalized": "/api/recordings/download/{}", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "media-or-binary", + "matched_route": "GET /api/recordings/download/#", + "matched_handler": "handle_recordings_download", + "domain": "recordings", + "status": "mapped", + "id": "UI-058" + }, + { + "file": "web/js/components/preact/SettingsView.jsx", + "line": 221, + "url_literal": "/api/settings", + "normalized": "/api/settings", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/settings", + "matched_handler": "handle_get_settings", + "domain": "settings", + "status": "mapped", + "id": "UI-059" + }, + { + "file": "web/js/components/preact/SettingsView.jsx", + "line": 233, + "url_literal": "/api/settings", + "normalized": "/api/settings", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/settings", + "matched_handler": "handle_post_settings", + "domain": "settings", + "status": "mapped", + "id": "UI-060" + }, + { + "file": "web/js/components/preact/SettingsView.jsx", + "line": 267, + "url_literal": "/api/auth/sessions", + "normalized": "/api/auth/sessions", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/auth/sessions", + "matched_handler": "handle_auth_sessions_list", + "domain": "settings", + "status": "mapped", + "id": "UI-061" + }, + { + "file": "web/js/components/preact/SettingsView.jsx", + "line": 279, + "url_literal": "/api/auth/trusted-devices", + "normalized": "/api/auth/trusted-devices", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/auth/trusted-devices", + "matched_handler": "handle_auth_trusted_devices_list", + "domain": "settings", + "status": "mapped", + "id": "UI-062" + }, + { + "file": "web/js/components/preact/SettingsView.jsx", + "line": 287, + "url_literal": "/api/auth/sessions/${sessionId}", + "normalized": "/api/auth/sessions/{}", + "method": "DELETE", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "DELETE /api/auth/sessions/#", + "matched_handler": "handle_auth_sessions_delete", + "domain": "settings", + "status": "mapped", + "id": "UI-063" + }, + { + "file": "web/js/components/preact/SettingsView.jsx", + "line": 299, + "url_literal": "/api/auth/trusted-devices/${deviceId}", + "normalized": "/api/auth/trusted-devices/{}", + "method": "DELETE", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "DELETE /api/auth/trusted-devices/#", + "matched_handler": "handle_auth_trusted_devices_delete", + "domain": "settings", + "status": "mapped", + "id": "UI-064" + }, + { + "file": "web/js/components/preact/SetupWizard.jsx", + "line": 216, + "url_literal": "/api/settings", + "normalized": "/api/settings", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/settings", + "matched_handler": "handle_get_settings", + "domain": "shared-shell", + "status": "mapped", + "id": "UI-065" + }, + { + "file": "web/js/components/preact/SetupWizard.jsx", + "line": 234, + "url_literal": "/api/system/info", + "normalized": "/api/system/info", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/system/info", + "matched_handler": "handle_get_system_info", + "domain": "shared-shell", + "status": "mapped", + "id": "UI-066" + }, + { + "file": "web/js/components/preact/SetupWizard.jsx", + "line": 259, + "url_literal": "/api/settings", + "normalized": "/api/settings", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/settings", + "matched_handler": "handle_post_settings", + "domain": "shared-shell", + "status": "mapped", + "id": "UI-067" + }, + { + "file": "web/js/components/preact/SetupWizard.jsx", + "line": 281, + "url_literal": "/api/setup/status", + "normalized": "/api/setup/status", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/setup/status", + "matched_handler": "handle_post_setup_complete", + "domain": "shared-shell", + "status": "mapped", + "id": "UI-068" + }, + { + "file": "web/js/components/preact/SetupWizard.jsx", + "line": 330, + "url_literal": "/api/setup/status", + "normalized": "/api/setup/status", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/setup/status", + "matched_handler": "handle_post_setup_complete", + "domain": "shared-shell", + "status": "mapped", + "id": "UI-069" + }, + { + "file": "web/js/components/preact/StreamCard.jsx", + "line": 140, + "url_literal": "${window.location.origin}/go2rtc/api/frame.jpeg?src=${encodeURIComponent(streamName)}&cache=5s&t=${ts}", + "normalized": "/go2rtc/api/frame.jpeg", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "go2rtc-proxy", + "matched_route": "GET /go2rtc/api/frame.jpeg", + "matched_handler": "handle_go2rtc_proxy", + "domain": "streams", + "status": "mapped", + "id": "UI-070" + }, + { + "file": "web/js/components/preact/StreamConfigModal.jsx", + "line": 336, + "url_literal": "/api/streams/${encodeURIComponent(currentStream.name)}/zones", + "normalized": "/api/streams/{}/zones", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/streams/#/zones", + "matched_handler": "handle_get_zones", + "domain": "streams", + "status": "mapped", + "id": "UI-071" + }, + { + "file": "web/js/components/preact/StreamsView.jsx", + "line": 199, + "url_literal": "/api/streams?${streamQueryParams.toString()}", + "normalized": "/api/streams", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/streams", + "matched_handler": "handle_get_streams", + "domain": "streams", + "status": "mapped", + "id": "UI-072" + }, + { + "file": "web/js/components/preact/StreamsView.jsx", + "line": 221, + "url_literal": "/api/detection/models", + "normalized": "/api/detection/models", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/detection/models", + "matched_handler": "handle_get_detection_models", + "domain": "streams", + "status": "mapped", + "id": "UI-073" + }, + { + "file": "web/js/components/preact/StreamsView.jsx", + "line": 229, + "url_literal": "/api/client-config", + "normalized": "/api/client-config", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/client-config", + "matched_handler": "handle_get_client_config", + "domain": "streams", + "status": "mapped", + "id": "UI-074" + }, + { + "file": "web/js/components/preact/StreamsView.jsx", + "line": 343, + "url_literal": "/api/streams/${encodeURIComponent(streamData.name)}", + "normalized": "/api/streams/{}", + "method": "PUT", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "PUT /api/streams/#", + "matched_handler": "handle_put_stream", + "domain": "streams", + "status": "mapped", + "id": "UI-075" + }, + { + "file": "web/js/components/preact/StreamsView.jsx", + "line": 358, + "url_literal": "/api/streams", + "normalized": "/api/streams", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/streams", + "matched_handler": "handle_post_stream", + "domain": "streams", + "status": "mapped", + "id": "UI-076" + }, + { + "file": "web/js/components/preact/StreamsView.jsx", + "line": 406, + "url_literal": "/api/streams/test", + "normalized": "/api/streams/test", + "method": "POST", + "method_evidence": "hand-reviewed: usePostMutation (previous line)", + "transport": "json", + "matched_route": "POST /api/streams/test", + "matched_handler": "handle_test_stream", + "domain": "streams", + "status": "mapped", + "id": "UI-077" + }, + { + "file": "web/js/components/preact/StreamsView.jsx", + "line": 433, + "url_literal": "/api/streams/${encodeURIComponent(streamId)}?permanent=true", + "normalized": "/api/streams/{}", + "method": "DELETE", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "DELETE /api/streams/#", + "matched_handler": "handle_delete_stream", + "domain": "streams", + "status": "mapped", + "id": "UI-078" + }, + { + "file": "web/js/components/preact/StreamsView.jsx", + "line": 459, + "url_literal": "/api/streams/${encodeURIComponent(streamId)}", + "normalized": "/api/streams/{}", + "method": "DELETE", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "DELETE /api/streams/#", + "matched_handler": "handle_delete_stream", + "domain": "streams", + "status": "mapped", + "id": "UI-079" + }, + { + "file": "web/js/components/preact/StreamsView.jsx", + "line": 484, + "url_literal": "/api/streams/${encodeURIComponent(streamId)}", + "normalized": "/api/streams/{}", + "method": "PUT", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "PUT /api/streams/#", + "matched_handler": "handle_put_stream", + "domain": "streams", + "status": "mapped", + "id": "UI-080" + }, + { + "file": "web/js/components/preact/StreamsView.jsx", + "line": 532, + "url_literal": "/api/streams/${encodeURIComponent(name)}", + "normalized": "/api/streams/{}", + "method": "PUT", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "PUT /api/streams/#", + "matched_handler": "handle_put_stream", + "domain": "streams", + "status": "mapped", + "id": "UI-081" + }, + { + "file": "web/js/components/preact/StreamsView.jsx", + "line": 562, + "url_literal": "/api/streams/${encodeURIComponent(name)}", + "normalized": "/api/streams/{}", + "method": "DELETE", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "DELETE /api/streams/#", + "matched_handler": "handle_delete_stream", + "domain": "streams", + "status": "mapped", + "id": "UI-082" + }, + { + "file": "web/js/components/preact/StreamsView.jsx", + "line": 583, + "url_literal": "/api/streams/${encodeURIComponent(name)}?permanent=true", + "normalized": "/api/streams/{}", + "method": "DELETE", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "DELETE /api/streams/#", + "matched_handler": "handle_delete_stream", + "domain": "streams", + "status": "mapped", + "id": "UI-083" + }, + { + "file": "web/js/components/preact/StreamsView.jsx", + "line": 808, + "url_literal": "/api/streams/${encodeURIComponent(streamId)}/full", + "normalized": "/api/streams/{}/full", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/streams/#/full", + "matched_handler": "handle_get_stream_full", + "domain": "streams", + "status": "mapped", + "id": "UI-084" + }, + { + "file": "web/js/components/preact/StreamsView.jsx", + "line": 900, + "url_literal": "/api/streams/${encodeURIComponent(streamId)}/full", + "normalized": "/api/streams/{}/full", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/streams/#/full", + "matched_handler": "handle_get_stream_full", + "domain": "streams", + "status": "mapped", + "id": "UI-085" + }, + { + "file": "web/js/components/preact/StreamsView.jsx", + "line": 992, + "url_literal": "/api/onvif/devices", + "normalized": "/api/onvif/devices", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/onvif/devices", + "matched_handler": "handle_get_discovered_onvif_devices", + "domain": "streams", + "status": "mapped", + "id": "UI-086" + }, + { + "file": "web/js/components/preact/StreamsView.jsx", + "line": 999, + "url_literal": "/api/settings", + "normalized": "/api/settings", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/settings", + "matched_handler": "handle_get_settings", + "domain": "streams", + "status": "mapped", + "id": "UI-087" + }, + { + "file": "web/js/components/preact/StreamsView.jsx", + "line": 1085, + "url_literal": "/api/onvif/discovery/discover", + "normalized": "/api/onvif/discovery/discover", + "method": "POST", + "method_evidence": "hand-reviewed: usePostMutation (previous line)", + "transport": "json", + "matched_route": "POST /api/onvif/discovery/discover", + "matched_handler": "handle_post_discover_onvif_devices", + "domain": "streams", + "status": "mapped", + "id": "UI-088" + }, + { + "file": "web/js/components/preact/StreamsView.jsx", + "line": 1163, + "url_literal": "/api/onvif/device/profiles", + "normalized": "/api/onvif/device/profiles", + "method": "GET", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "GET /api/onvif/device/profiles", + "matched_handler": "handle_get_onvif_device_profiles", + "domain": "streams", + "status": "mapped", + "id": "UI-089" + }, + { + "file": "web/js/components/preact/StreamsView.jsx", + "line": 1216, + "url_literal": "/api/onvif/device/test", + "normalized": "/api/onvif/device/test", + "method": "POST", + "method_evidence": "hand-reviewed: usePostMutation (previous line)", + "transport": "json", + "matched_route": "POST /api/onvif/device/test", + "matched_handler": "handle_post_test_onvif_connection", + "domain": "streams", + "status": "mapped", + "id": "UI-090" + }, + { + "file": "web/js/components/preact/StreamsView.jsx", + "line": 1286, + "url_literal": "/api/onvif/device/claim", + "normalized": "/api/onvif/device/claim", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/onvif/device/claim", + "matched_handler": "handle_post_claim_onvif_device", + "domain": "streams", + "status": "mapped", + "id": "UI-091" + }, + { + "file": "web/js/components/preact/SystemView.jsx", + "line": 139, + "url_literal": "/api/system/info", + "normalized": "/api/system/info", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/system/info", + "matched_handler": "handle_get_system_info", + "domain": "system-health", + "status": "mapped", + "id": "UI-092" + }, + { + "file": "web/js/components/preact/SystemView.jsx", + "line": 151, + "url_literal": "/api/system/logs/clear", + "normalized": "/api/system/logs/clear", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/system/logs/clear", + "matched_handler": "handle_post_system_logs_clear", + "domain": "system-health", + "status": "mapped", + "id": "UI-093" + }, + { + "file": "web/js/components/preact/SystemView.jsx", + "line": 199, + "url_literal": "/api/system/restart", + "normalized": "/api/system/restart", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/system/restart", + "matched_handler": "handle_post_system_restart", + "domain": "system-health", + "status": "mapped", + "id": "UI-094" + }, + { + "file": "web/js/components/preact/UI.jsx", + "line": 292, + "url_literal": "/api/recordings/${recordingId}", + "normalized": "/api/recordings/{}", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/recordings/#", + "matched_handler": "handle_get_recording", + "domain": "shared-ui-primitives", + "status": "mapped", + "id": "UI-095" + }, + { + "file": "web/js/components/preact/UI.jsx", + "line": 320, + "url_literal": "/api/detection/results/${data.stream}?start=${startTime}&end=${endTime}", + "normalized": "/api/detection/results/{}", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/detection/results/#", + "matched_handler": "handle_get_detection_results", + "domain": "shared-ui-primitives", + "status": "mapped", + "id": "UI-096" + }, + { + "file": "web/js/components/preact/UI.jsx", + "line": 367, + "url_literal": "/api/recordings/${recordingId}/protect", + "normalized": "/api/recordings/{}/protect", + "method": "PUT", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "PUT /api/recordings/#/protect", + "matched_handler": "handle_put_recording_protect", + "domain": "shared-ui-primitives", + "status": "mapped", + "id": "UI-097" + }, + { + "file": "web/js/components/preact/UI.jsx", + "line": 393, + "url_literal": "/api/recordings/${recordingId}", + "normalized": "/api/recordings/{}", + "method": "DELETE", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "DELETE /api/recordings/#", + "matched_handler": "handle_delete_recording", + "domain": "shared-ui-primitives", + "status": "mapped", + "id": "UI-098" + }, + { + "file": "web/js/components/preact/UsersView.jsx", + "line": 64, + "url_literal": "/api/auth/users", + "normalized": "/api/auth/users", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/auth/users", + "matched_handler": "handle_users_list", + "domain": "users-access", + "status": "mapped", + "id": "UI-099" + }, + { + "file": "web/js/components/preact/UsersView.jsx", + "line": 115, + "url_literal": "/api/auth/users", + "normalized": "/api/auth/users", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/auth/users", + "matched_handler": "handle_users_create", + "domain": "users-access", + "status": "mapped", + "id": "UI-100" + }, + { + "file": "web/js/components/preact/UsersView.jsx", + "line": 144, + "url_literal": "/api/auth/users/${userId}", + "normalized": "/api/auth/users/{}", + "method": "PUT", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "PUT /api/auth/users/#", + "matched_handler": "handle_users_update", + "domain": "users-access", + "status": "mapped", + "id": "UI-101" + }, + { + "file": "web/js/components/preact/UsersView.jsx", + "line": 173, + "url_literal": "/api/auth/users/${userId}", + "normalized": "/api/auth/users/{}", + "method": "DELETE", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "DELETE /api/auth/users/#", + "matched_handler": "handle_users_delete", + "domain": "users-access", + "status": "mapped", + "id": "UI-102" + }, + { + "file": "web/js/components/preact/UsersView.jsx", + "line": 197, + "url_literal": "/api/auth/users/${userId}/login-lockout/clear", + "normalized": "/api/auth/users/{}/login-lockout/clear", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/auth/users/#/login-lockout/clear", + "matched_handler": "handle_users_clear_login_lockout", + "domain": "users-access", + "status": "mapped", + "id": "UI-103" + }, + { + "file": "web/js/components/preact/WebRTCVideoCell.jsx", + "line": 347, + "url_literal": "/api/ice-servers", + "normalized": "/api/ice-servers", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/ice-servers", + "matched_handler": "handle_get_ice_servers", + "domain": "live", + "status": "mapped", + "id": "UI-104" + }, + { + "file": "web/js/components/preact/WebRTCVideoCell.jsx", + "line": 852, + "url_literal": "${go2rtcBaseUrl}/api/webrtc?src=${encodeURIComponent(effectiveName)}", + "normalized": "/go2rtc/api/webrtc", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "go2rtc-proxy", + "matched_route": "POST /go2rtc/api/webrtc", + "matched_handler": "handle_go2rtc_proxy", + "domain": "live", + "status": "mapped", + "id": "UI-105" + }, + { + "file": "web/js/components/preact/WebRTCVideoCell.jsx", + "line": 1123, + "url_literal": "/api/streams/${encodeURIComponent(stream.name)}/refresh", + "normalized": "/api/streams/{}/refresh", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/streams/#/refresh", + "matched_handler": "handle_post_stream_refresh", + "domain": "live", + "status": "mapped", + "id": "UI-106" + }, + { + "file": "web/js/components/preact/WebRTCVideoCell.jsx", + "line": 1201, + "url_literal": "/api/streams/${encodeURIComponent(stream.name)}", + "normalized": "/api/streams/{}", + "method": "PUT", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "PUT /api/streams/#", + "matched_handler": "handle_put_stream", + "domain": "live", + "status": "mapped", + "id": "UI-107" + }, + { + "file": "web/js/components/preact/WebRTCVideoCell.jsx", + "line": 1224, + "url_literal": "/api/streams/${encodeURIComponent(stream.name)}", + "normalized": "/api/streams/{}", + "method": "PUT", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "PUT /api/streams/#", + "matched_handler": "handle_put_stream", + "domain": "live", + "status": "mapped", + "id": "UI-108" + }, + { + "file": "web/js/components/preact/ZoneEditor.jsx", + "line": 53, + "url_literal": "/api/streams/${encodeURIComponent(streamName)}/zones", + "normalized": "/api/streams/{}/zones", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/streams/#/zones", + "matched_handler": "handle_get_zones", + "domain": "streams", + "status": "mapped", + "id": "UI-109" + }, + { + "file": "web/js/components/preact/ZoneEditor.jsx", + "line": 82, + "url_literal": "${go2rtcBaseUrl}/api/frame.jpeg?src=${encodeURIComponent(streamName)}&t=${timestamp}", + "normalized": "/go2rtc/api/frame.jpeg", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "go2rtc-proxy", + "matched_route": "GET /go2rtc/api/frame.jpeg", + "matched_handler": "handle_go2rtc_proxy", + "domain": "streams", + "status": "mapped", + "id": "UI-110" + }, + { + "file": "web/js/components/preact/ZoneEditor.jsx", + "line": 88, + "url_literal": "${window.location.origin}/go2rtc/api/frame.jpeg?src=${encodeURIComponent(streamName)}&t=${timestamp}", + "normalized": "/go2rtc/api/frame.jpeg", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "go2rtc-proxy", + "matched_route": "GET /go2rtc/api/frame.jpeg", + "matched_handler": "handle_go2rtc_proxy", + "domain": "streams", + "status": "mapped", + "id": "UI-111" + }, + { + "file": "web/js/components/preact/ZoneEditor.jsx", + "line": 481, + "url_literal": "/api/streams/${encodeURIComponent(streamName)}/zones", + "normalized": "/api/streams/{}/zones", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/streams/#/zones", + "matched_handler": "handle_post_zones", + "domain": "streams", + "status": "mapped", + "id": "UI-112" + }, + { + "file": "web/js/components/preact/fleet/CollectionManager.jsx", + "line": 40, + "url_literal": "/api/camera-collections/${collection.uuid}", + "normalized": "/api/camera-collections/{}", + "method": "PUT", + "method_evidence": "hand-reviewed ternary: method method: collection ? 'PUT' : 'POST' (URL #1 on line)", + "transport": "json", + "matched_route": "PUT /api/camera-collections/#", + "matched_handler": "handle_put_camera_collection", + "domain": "fleet", + "status": "mapped", + "id": "UI-113" + }, + { + "file": "web/js/components/preact/fleet/CollectionManager.jsx", + "line": 40, + "url_literal": "/api/camera-collections", + "normalized": "/api/camera-collections", + "method": "POST", + "method_evidence": "hand-reviewed ternary: method method: collection ? 'PUT' : 'POST' (URL #2 on line)", + "transport": "json", + "matched_route": "POST /api/camera-collections", + "matched_handler": "handle_post_camera_collection", + "domain": "fleet", + "status": "mapped", + "id": "UI-114" + }, + { + "file": "web/js/components/preact/fleet/CollectionManager.jsx", + "line": 48, + "url_literal": "/api/camera-collections/${saved.uuid}/members", + "normalized": "/api/camera-collections/{}/members", + "method": "PUT", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "PUT /api/camera-collections/#/members", + "matched_handler": "handle_put_camera_collection_members", + "domain": "fleet", + "status": "mapped", + "id": "UI-115" + }, + { + "file": "web/js/components/preact/fleet/CollectionManager.jsx", + "line": 177, + "url_literal": "/api/camera-collections/${collection.uuid}/preview", + "normalized": "/api/camera-collections/{}/preview", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/camera-collections/#/preview", + "matched_handler": "handle_post_camera_collection_preview", + "domain": "fleet", + "status": "mapped", + "id": "UI-116" + }, + { + "file": "web/js/components/preact/fleet/CollectionManager.jsx", + "line": 193, + "url_literal": "/api/camera-collections/${collection.uuid}/members", + "normalized": "/api/camera-collections/{}/members", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/camera-collections/#/members", + "matched_handler": "handle_get_camera_collection_members", + "domain": "fleet", + "status": "mapped", + "id": "UI-117" + }, + { + "file": "web/js/components/preact/fleet/CollectionManager.jsx", + "line": 206, + "url_literal": "/api/camera-collections/${collection.uuid}", + "normalized": "/api/camera-collections/{}", + "method": "DELETE", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "DELETE /api/camera-collections/#", + "matched_handler": "handle_delete_camera_collection", + "domain": "fleet", + "status": "mapped", + "id": "UI-118" + }, + { + "file": "web/js/components/preact/fleet/CollectionSelectorBuilder.jsx", + "line": 52, + "url_literal": "/api/fleet/cameras/query", + "normalized": "/api/fleet/cameras/query", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/fleet/cameras/query", + "matched_handler": "handle_post_fleet_camera_query", + "domain": "fleet", + "status": "mapped", + "id": "UI-119" + }, + { + "file": "web/js/components/preact/fleet/CollectionSelectorBuilder.jsx", + "line": 88, + "url_literal": "/api/fleet/cameras/query", + "normalized": "/api/fleet/cameras/query", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/fleet/cameras/query", + "matched_handler": "handle_post_fleet_camera_query", + "domain": "fleet", + "status": "mapped", + "id": "UI-120" + }, + { + "file": "web/js/components/preact/fleet/CollectionSelectorBuilder.jsx", + "line": 306, + "url_literal": "/api/fleet/selectors/preview", + "normalized": "/api/fleet/selectors/preview", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/fleet/selectors/preview", + "matched_handler": "handle_post_fleet_selector_preview", + "domain": "fleet", + "status": "mapped", + "id": "UI-121" + }, + { + "file": "web/js/components/preact/fleet/OrganizationManager.jsx", + "line": 25, + "url_literal": "/api/locations/${item.uuid}", + "normalized": "/api/locations/{}", + "method": "PUT", + "method_evidence": "hand-reviewed ternary: method method: item ? 'PUT' : 'POST' (URL #1 on line)", + "transport": "json", + "matched_route": "PUT /api/locations/#", + "matched_handler": "handle_put_location", + "domain": "fleet", + "status": "mapped", + "id": "UI-122" + }, + { + "file": "web/js/components/preact/fleet/OrganizationManager.jsx", + "line": 25, + "url_literal": "/api/locations", + "normalized": "/api/locations", + "method": "POST", + "method_evidence": "hand-reviewed ternary: method method: item ? 'PUT' : 'POST' (URL #2 on line)", + "transport": "json", + "matched_route": "POST /api/locations", + "matched_handler": "handle_post_location", + "domain": "fleet", + "status": "mapped", + "id": "UI-123" + }, + { + "file": "web/js/components/preact/fleet/OrganizationManager.jsx", + "line": 89, + "url_literal": "/api/locations/${location.uuid}", + "normalized": "/api/locations/{}", + "method": "DELETE", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "DELETE /api/locations/#", + "matched_handler": "handle_delete_location", + "domain": "fleet", + "status": "mapped", + "id": "UI-124" + }, + { + "file": "web/js/components/preact/fleet/OrganizationManager.jsx", + "line": 172, + "url_literal": "/api/camera-tags/${item.uuid}/merge", + "normalized": "/api/camera-tags/{}/merge", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/camera-tags/#/merge", + "matched_handler": "handle_post_camera_tag_merge", + "domain": "fleet", + "status": "mapped", + "id": "UI-125" + }, + { + "file": "web/js/components/preact/fleet/OrganizationManager.jsx", + "line": 177, + "url_literal": "/api/camera-tags/${item.uuid}", + "normalized": "/api/camera-tags/{}", + "method": "PUT", + "method_evidence": "hand-reviewed ternary: method method: item ? 'PUT' : 'POST' (URL #1 on line)", + "transport": "json", + "matched_route": "PUT /api/camera-tags/#", + "matched_handler": "handle_put_camera_tag", + "domain": "fleet", + "status": "mapped", + "id": "UI-126" + }, + { + "file": "web/js/components/preact/fleet/OrganizationManager.jsx", + "line": 177, + "url_literal": "/api/camera-tags", + "normalized": "/api/camera-tags", + "method": "POST", + "method_evidence": "hand-reviewed ternary: method method: item ? 'PUT' : 'POST' (URL #2 on line)", + "transport": "json", + "matched_route": "POST /api/camera-tags", + "matched_handler": "handle_post_camera_tag", + "domain": "fleet", + "status": "mapped", + "id": "UI-127" + }, + { + "file": "web/js/components/preact/fleet/OrganizationManager.jsx", + "line": 237, + "url_literal": "/api/camera-tags/${tag.uuid}", + "normalized": "/api/camera-tags/{}", + "method": "DELETE", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "DELETE /api/camera-tags/#", + "matched_handler": "handle_delete_camera_tag", + "domain": "fleet", + "status": "mapped", + "id": "UI-128" + }, + { + "file": "web/js/components/preact/fleet/SavedViewsBar.jsx", + "line": 33, + "url_literal": "/api/fleet/views", + "normalized": "/api/fleet/views", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/fleet/views", + "matched_handler": "handle_post_fleet_saved_view", + "domain": "fleet", + "status": "mapped", + "id": "UI-129" + }, + { + "file": "web/js/components/preact/fleet/SavedViewsBar.jsx", + "line": 54, + "url_literal": "/api/fleet/views/${encodeURIComponent(view.uuid)}", + "normalized": "/api/fleet/views/{}", + "method": "DELETE", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "DELETE /api/fleet/views/#", + "matched_handler": "handle_delete_fleet_saved_view", + "domain": "fleet", + "status": "mapped", + "id": "UI-130" + }, + { + "file": "web/js/components/preact/fleet/StaticCollectionMembers.jsx", + "line": 29, + "url_literal": "/api/fleet/cameras/query", + "normalized": "/api/fleet/cameras/query", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/fleet/cameras/query", + "matched_handler": "handle_post_fleet_camera_query", + "domain": "fleet", + "status": "mapped", + "id": "UI-131" + }, + { + "file": "web/js/components/preact/fleet/collectionMembership.js", + "line": 14, + "url_literal": "/api/fleet/cameras/query", + "normalized": "/api/fleet/cameras/query", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/fleet/cameras/query", + "matched_handler": "handle_post_fleet_camera_query", + "domain": "fleet", + "status": "mapped", + "id": "UI-132" + }, + { + "file": "web/js/components/preact/fleet/collectionMembership.js", + "line": 42, + "url_literal": "/api/camera-collections", + "normalized": "/api/camera-collections", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/camera-collections", + "matched_handler": "handle_get_camera_collections", + "domain": "fleet", + "status": "mapped", + "id": "UI-133" + }, + { + "file": "web/js/components/preact/fleet/fleetOrganization.js", + "line": 100, + "url_literal": "/api/cameras/${encodeURIComponent(camera.camera_uuid)}/location", + "normalized": "/api/cameras/{}/location", + "method": "PUT", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "PUT /api/cameras/#/location", + "matched_handler": "handle_put_camera_location", + "domain": "fleet", + "status": "mapped", + "id": "UI-134" + }, + { + "file": "web/js/components/preact/fleet/fleetOrganization.js", + "line": 110, + "url_literal": "/api/cameras/${encodeURIComponent(camera.camera_uuid)}/tags", + "normalized": "/api/cameras/{}/tags", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/cameras/#/tags", + "matched_handler": "handle_get_camera_tag_assignments", + "domain": "fleet", + "status": "mapped", + "id": "UI-135" + }, + { + "file": "web/js/components/preact/fleet/fleetOrganization.js", + "line": 122, + "url_literal": "/api/cameras/${encodeURIComponent(camera.camera_uuid)}/tags", + "normalized": "/api/cameras/{}/tags", + "method": "PUT", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "PUT /api/cameras/#/tags", + "matched_handler": "handle_put_camera_tag_assignments", + "domain": "fleet", + "status": "mapped", + "id": "UI-136" + }, + { + "file": "web/js/components/preact/investigation/InvestigationActions.jsx", + "line": 26, + "url_literal": "/api/recordings/batch-download/result/${encodeURIComponent(token)}", + "normalized": "/api/recordings/batch-download/result/{}", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/recordings/batch-download/result/#", + "matched_handler": "handle_batch_download_result", + "domain": "investigation", + "status": "mapped", + "id": "UI-137" + }, + { + "file": "web/js/components/preact/investigation/InvestigationActions.jsx", + "line": 66, + "url_literal": "/api/investigations/recordings/preview", + "normalized": "/api/investigations/recordings/preview", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/investigations/recordings/preview", + "matched_handler": "handle_post_investigation_recording_preview", + "domain": "investigation", + "status": "mapped", + "id": "UI-138" + }, + { + "file": "web/js/components/preact/investigation/InvestigationActions.jsx", + "line": 129, + "url_literal": "/api/recordings/batch-protect", + "normalized": "/api/recordings/batch-protect", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/recordings/batch-protect", + "matched_handler": "handle_batch_protect_recordings", + "domain": "investigation", + "status": "mapped", + "id": "UI-139" + }, + { + "file": "web/js/components/preact/investigation/InvestigationActions.jsx", + "line": 164, + "url_literal": "/api/recordings/batch-download", + "normalized": "/api/recordings/batch-download", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/recordings/batch-download", + "matched_handler": "handle_batch_download_recordings", + "domain": "investigation", + "status": "mapped", + "id": "UI-140" + }, + { + "file": "web/js/components/preact/investigation/InvestigationActions.jsx", + "line": 179, + "url_literal": "/api/recordings/batch-download/status/${encodeURIComponent(job.token)}", + "normalized": "/api/recordings/batch-download/status/{}", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/recordings/batch-download/status/#", + "matched_handler": "handle_batch_download_status", + "domain": "investigation", + "status": "mapped", + "id": "UI-141" + }, + { + "file": "web/js/components/preact/investigation/InvestigationBookmarks.jsx", + "line": 50, + "url_literal": "/api/investigation-bookmarks", + "normalized": "/api/investigation-bookmarks", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/investigation-bookmarks", + "matched_handler": "handle_get_investigation_bookmarks", + "domain": "investigation", + "status": "mapped", + "id": "UI-142" + }, + { + "file": "web/js/components/preact/investigation/InvestigationBookmarks.jsx", + "line": 80, + "url_literal": "/api/investigation-bookmarks", + "normalized": "/api/investigation-bookmarks", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/investigation-bookmarks", + "matched_handler": "handle_post_investigation_bookmark", + "domain": "investigation", + "status": "mapped", + "id": "UI-143" + }, + { + "file": "web/js/components/preact/investigation/InvestigationBookmarks.jsx", + "line": 103, + "url_literal": "/api/investigation-bookmarks/${bookmark.uuid}", + "normalized": "/api/investigation-bookmarks/{}", + "method": "DELETE", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "DELETE /api/investigation-bookmarks/#", + "matched_handler": "handle_delete_investigation_bookmark", + "domain": "investigation", + "status": "mapped", + "id": "UI-144" + }, + { + "file": "web/js/components/preact/investigation/InvestigationView.jsx", + "line": 150, + "url_literal": "/api/investigations/segment-at", + "normalized": "/api/investigations/segment-at", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/investigations/segment-at", + "matched_handler": "handle_post_investigation_segment_at", + "domain": "investigation", + "status": "mapped", + "id": "UI-145" + }, + { + "file": "web/js/components/preact/investigation/InvestigationView.jsx", + "line": 238, + "url_literal": "/api/recordings/play/${segment.id}", + "normalized": "/api/recordings/play/{}", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "media-or-binary", + "matched_route": "GET /api/recordings/play/#", + "matched_handler": "handle_recordings_playback", + "domain": "investigation", + "status": "mapped", + "id": "UI-146" + }, + { + "file": "web/js/components/preact/investigation/InvestigationView.jsx", + "line": 878, + "url_literal": "/api/investigations/search", + "normalized": "/api/investigations/search", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/investigations/search", + "matched_handler": "handle_post_investigation_search", + "domain": "investigation", + "status": "mapped", + "id": "UI-147" + }, + { + "file": "web/js/components/preact/investigation/InvestigationView.jsx", + "line": 897, + "url_literal": "/api/investigations/search", + "normalized": "/api/investigations/search", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/investigations/search", + "matched_handler": "handle_post_investigation_search", + "domain": "investigation", + "status": "mapped", + "id": "UI-148" + }, + { + "file": "web/js/components/preact/investigation/InvestigationView.jsx", + "line": 987, + "url_literal": "/api/investigations/timeline", + "normalized": "/api/investigations/timeline", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/investigations/timeline", + "matched_handler": "handle_post_investigation_timeline", + "domain": "investigation", + "status": "mapped", + "id": "UI-149" + }, + { + "file": "web/js/components/preact/investigation/InvestigationView.jsx", + "line": 1262, + "url_literal": "/api/investigations/thumbnail-samples", + "normalized": "/api/investigations/thumbnail-samples", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "media-or-binary", + "matched_route": "POST /api/investigations/thumbnail-samples", + "matched_handler": "handle_post_investigation_thumbnail_samples", + "domain": "investigation", + "status": "mapped", + "id": "UI-150" + }, + { + "file": "web/js/components/preact/live/LiveBuildingPlan.jsx", + "line": 67, + "url_literal": "/api/live/plans/${plan.uuid}/background?v=${version}", + "normalized": "/api/live/plans/{}/background", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "media-or-binary", + "matched_route": "GET /api/live/plans/#/background", + "matched_handler": "handle_get_operator_floor_plan_background", + "domain": "live", + "status": "mapped", + "id": "UI-151" + }, + { + "file": "web/js/components/preact/live/LiveBuildingPlan.jsx", + "line": 90, + "url_literal": "/go2rtc/api/frame.jpeg?src=${encodeURIComponent(stream.name)}&cache=5s&t=${Date.now()}", + "normalized": "/go2rtc/api/frame.jpeg", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "go2rtc-proxy", + "matched_route": "GET /go2rtc/api/frame.jpeg", + "matched_handler": "handle_go2rtc_proxy", + "domain": "live", + "status": "mapped", + "id": "UI-152" + }, + { + "file": "web/js/components/preact/live/LiveBuildingPlan.jsx", + "line": 207, + "url_literal": "/api/live/plans", + "normalized": "/api/live/plans", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/live/plans", + "matched_handler": "handle_get_operator_floor_plans", + "domain": "live", + "status": "mapped", + "id": "UI-153" + }, + { + "file": "web/js/components/preact/live/LiveBuildingPlan.jsx", + "line": 318, + "url_literal": "/api/live/plans", + "normalized": "/api/live/plans", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/live/plans", + "matched_handler": "handle_post_operator_floor_plan", + "domain": "live", + "status": "mapped", + "id": "UI-154" + }, + { + "file": "web/js/components/preact/live/LiveBuildingPlan.jsx", + "line": 350, + "url_literal": "/api/live/plans/${plan.uuid}", + "normalized": "/api/live/plans/{}", + "method": "PUT", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "PUT /api/live/plans/#", + "matched_handler": "handle_put_operator_floor_plan", + "domain": "live", + "status": "mapped", + "id": "UI-155" + }, + { + "file": "web/js/components/preact/live/LiveBuildingPlan.jsx", + "line": 371, + "url_literal": "/api/live/plans/${plan.uuid}", + "normalized": "/api/live/plans/{}", + "method": "DELETE", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "DELETE /api/live/plans/#", + "matched_handler": "handle_delete_operator_floor_plan", + "domain": "live", + "status": "mapped", + "id": "UI-156" + }, + { + "file": "web/js/components/preact/live/LiveBuildingPlan.jsx", + "line": 408, + "url_literal": "/api/live/plans/${plan.uuid}/background", + "normalized": "/api/live/plans/{}/background", + "method": "PUT", + "method_evidence": "explicit method option", + "transport": "media-or-binary", + "matched_route": "PUT /api/live/plans/#/background", + "matched_handler": "handle_put_operator_floor_plan_background", + "domain": "live", + "status": "mapped", + "id": "UI-157" + }, + { + "file": "web/js/components/preact/live/LiveBuildingPlan.jsx", + "line": 433, + "url_literal": "/api/live/plans/${plan.uuid}", + "normalized": "/api/live/plans/{}", + "method": "PUT", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "PUT /api/live/plans/#", + "matched_handler": "handle_put_operator_floor_plan", + "domain": "live", + "status": "mapped", + "id": "UI-158" + }, + { + "file": "web/js/components/preact/live/LiveBuildingPlan.jsx", + "line": 460, + "url_literal": "/api/live/plans/${plan.uuid}/background", + "normalized": "/api/live/plans/{}/background", + "method": "DELETE", + "method_evidence": "explicit method option", + "transport": "media-or-binary", + "matched_route": "DELETE /api/live/plans/#/background", + "matched_handler": "handle_delete_operator_floor_plan_background", + "domain": "live", + "status": "mapped", + "id": "UI-159" + }, + { + "file": "web/js/components/preact/live/LiveBuildingPlan.jsx", + "line": 660, + "url_literal": "/api/live/plans/${plan.uuid}", + "normalized": "/api/live/plans/{}", + "method": "PUT", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "PUT /api/live/plans/#", + "matched_handler": "handle_put_operator_floor_plan", + "domain": "live", + "status": "mapped", + "id": "UI-160" + }, + { + "file": "web/js/components/preact/live/LiveOperatorNavigator.jsx", + "line": 92, + "url_literal": "/api/ui/workspaces", + "normalized": "/api/ui/workspaces", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/ui/workspaces", + "matched_handler": "handle_get_ui_workspaces", + "domain": "live", + "status": "mapped", + "id": "UI-161" + }, + { + "file": "web/js/components/preact/live/LiveOperatorNavigator.jsx", + "line": 97, + "url_literal": "/api/locations", + "normalized": "/api/locations", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/locations", + "matched_handler": "handle_get_locations", + "domain": "live", + "status": "mapped", + "id": "UI-162" + }, + { + "file": "web/js/components/preact/live/LiveOperatorNavigator.jsx", + "line": 102, + "url_literal": "/api/live/layouts", + "normalized": "/api/live/layouts", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/live/layouts", + "matched_handler": "handle_get_live_layouts", + "domain": "live", + "status": "mapped", + "id": "UI-163" + }, + { + "file": "web/js/components/preact/live/LiveOperatorNavigator.jsx", + "line": 107, + "url_literal": "/api/live/plans", + "normalized": "/api/live/plans", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/live/plans", + "matched_handler": "handle_get_operator_floor_plans", + "domain": "live", + "status": "mapped", + "id": "UI-164" + }, + { + "file": "web/js/components/preact/live/LiveOperatorNavigator.jsx", + "line": 149, + "url_literal": "/api/investigations/search", + "normalized": "/api/investigations/search", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/investigations/search", + "matched_handler": "handle_post_investigation_search", + "domain": "live", + "status": "mapped", + "id": "UI-165" + }, + { + "file": "web/js/components/preact/live/LiveOperatorNavigator.jsx", + "line": 235, + "url_literal": "/api/live/layouts", + "normalized": "/api/live/layouts", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/live/layouts", + "matched_handler": "handle_post_live_layout", + "domain": "live", + "status": "mapped", + "id": "UI-166" + }, + { + "file": "web/js/components/preact/live/LiveOperatorNavigator.jsx", + "line": 265, + "url_literal": "/api/live/layouts/${layout.uuid}", + "normalized": "/api/live/layouts/{}", + "method": "DELETE", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "DELETE /api/live/layouts/#", + "matched_handler": "handle_delete_live_layout", + "domain": "live", + "status": "mapped", + "id": "UI-167" + }, + { + "file": "web/js/components/preact/live/cameraFavorites.js", + "line": 41, + "url_literal": "/api/camera-favorites/${encodeURIComponent(cameraUuid)}", + "normalized": "/api/camera-favorites/{}", + "method": "PUT", + "method_evidence": "hand-reviewed: method: favorite ? 'PUT' : 'DELETE'", + "transport": "json", + "matched_route": "PUT /api/camera-favorites/#", + "matched_handler": "handle_put_camera_favorite", + "domain": "live", + "status": "mapped", + "id": "UI-168" + }, + { + "file": "web/js/components/preact/live/cameraFavorites.js", + "line": 41, + "url_literal": "/api/camera-favorites/${encodeURIComponent(cameraUuid)}", + "normalized": "/api/camera-favorites/{}", + "method": "DELETE", + "method_evidence": "hand-reviewed: method: favorite ? 'PUT' : 'DELETE'", + "transport": "json", + "matched_route": "DELETE /api/camera-favorites/#", + "matched_handler": "handle_delete_camera_favorite", + "domain": "live", + "status": "mapped", + "id": "UI-169" + }, + { + "file": "web/js/components/preact/live/cameraFavorites.js", + "line": 47, + "url_literal": "/api/camera-favorites", + "normalized": "/api/camera-favorites", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/camera-favorites", + "matched_handler": "handle_get_camera_favorites", + "domain": "live", + "status": "mapped", + "id": "UI-170" + }, + { + "file": "web/js/components/preact/lpr/lprSearch.js", + "line": 172, + "url_literal": "/api/lpr/search", + "normalized": "/api/lpr/search", + "method": "POST", + "method_evidence": "hand-reviewed: postJson helper", + "transport": "json", + "matched_route": "POST /api/lpr/search", + "matched_handler": "handle_post_lpr_search", + "domain": "lpr", + "status": "mapped", + "id": "UI-171" + }, + { + "file": "web/js/components/preact/lpr/lprSearch.js", + "line": 180, + "url_literal": "/api/lpr/export", + "normalized": "/api/lpr/export", + "method": "POST", + "method_evidence": "hand-reviewed: postJson helper", + "transport": "json", + "matched_route": "POST /api/lpr/export", + "matched_handler": "handle_post_lpr_export", + "domain": "lpr", + "status": "mapped", + "id": "UI-172" + }, + { + "file": "web/js/components/preact/lpr/lprSearch.js", + "line": 187, + "url_literal": "/api/lpr/reads/${encodeURIComponent(readId)}", + "normalized": "/api/lpr/reads/{}", + "method": "DELETE", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "DELETE /api/lpr/reads/#", + "matched_handler": "handle_delete_lpr_read", + "domain": "lpr", + "status": "mapped", + "id": "UI-173" + }, + { + "file": "web/js/components/preact/ptzActions.js", + "line": 2, + "url_literal": "/api/streams/${encodeURIComponent(streamName)}/ptz/${action}", + "normalized": "/api/streams/{}/ptz/capabilities", + "method": "GET", + "method_evidence": "hand-reviewed: ptzRequest(name,'capabilities')", + "transport": "json", + "matched_route": "GET /api/streams/#/ptz/capabilities", + "matched_handler": "handle_ptz_capabilities", + "domain": "live", + "status": "mapped", + "id": "UI-174" + }, + { + "file": "web/js/components/preact/ptzActions.js", + "line": 2, + "url_literal": "/api/streams/${encodeURIComponent(streamName)}/ptz/${action}", + "normalized": "/api/streams/{}/ptz/presets", + "method": "GET", + "method_evidence": "hand-reviewed: ptzRequest(name,'presets')", + "transport": "json", + "matched_route": "GET /api/streams/#/ptz/presets", + "matched_handler": "handle_ptz_get_presets", + "domain": "live", + "status": "mapped", + "id": "UI-175" + }, + { + "file": "web/js/components/preact/ptzActions.js", + "line": 2, + "url_literal": "/api/streams/${encodeURIComponent(streamName)}/ptz/${action}", + "normalized": "/api/streams/{}/ptz/preset", + "method": "PUT", + "method_evidence": "hand-reviewed: ptzRequest(name,'preset',{method:'PUT'})", + "transport": "json", + "matched_route": "PUT /api/streams/#/ptz/preset", + "matched_handler": "handle_ptz_set_preset", + "domain": "live", + "status": "mapped", + "id": "UI-176" + }, + { + "file": "web/js/components/preact/ptzActions.js", + "line": 2, + "url_literal": "/api/streams/${encodeURIComponent(streamName)}/ptz/${action}", + "normalized": "/api/streams/{}/ptz/set-home", + "method": "POST", + "method_evidence": "hand-reviewed: ptzRequest(name,'set-home',{method:'POST'})", + "transport": "json", + "matched_route": "POST /api/streams/#/ptz/set-home", + "matched_handler": "handle_ptz_set_home", + "domain": "live", + "status": "mapped", + "id": "UI-177" + }, + { + "file": "web/js/components/preact/recordings/RecordingsGrid.jsx", + "line": 131, + "url_literal": "/api/recordings/thumbnail/${recording.id}/0", + "normalized": "/api/recordings/thumbnail/{}/0", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "media-or-binary", + "matched_route": "GET /api/recordings/thumbnail/#/#", + "matched_handler": "handle_recordings_thumbnail", + "domain": "recordings", + "status": "mapped", + "id": "UI-178" + }, + { + "file": "web/js/components/preact/recordings/RecordingsGrid.jsx", + "line": 144, + "url_literal": "/api/recordings/thumbnail/${recording.id}/${currentFrame}", + "normalized": "/api/recordings/thumbnail/{}/{}", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "media-or-binary", + "matched_route": "GET /api/recordings/thumbnail/#/#", + "matched_handler": "handle_recordings_thumbnail", + "domain": "recordings", + "status": "mapped", + "id": "UI-179" + }, + { + "file": "web/js/components/preact/recordings/RecordingsGrid.jsx", + "line": 185, + "url_literal": "/api/recordings/thumbnail/${recording.id}/${i}", + "normalized": "/api/recordings/thumbnail/{}/{}", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "media-or-binary", + "matched_route": "GET /api/recordings/thumbnail/#/#", + "matched_handler": "handle_recordings_thumbnail", + "domain": "recordings", + "status": "mapped", + "id": "UI-180" + }, + { + "file": "web/js/components/preact/recordings/RecordingsGrid.jsx", + "line": 222, + "url_literal": "/api/recordings/thumbnail/${recording.id}/${currentFrame}", + "normalized": "/api/recordings/thumbnail/{}/{}", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "media-or-binary", + "matched_route": "GET /api/recordings/thumbnail/#/#", + "matched_handler": "handle_recordings_thumbnail", + "domain": "recordings", + "status": "mapped", + "id": "UI-181" + }, + { + "file": "web/js/components/preact/recordings/recordingsAPI.jsx", + "line": 166, + "url_literal": "/api/recordings/${recordingId}", + "normalized": "/api/recordings/{}", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/recordings/#", + "matched_handler": "handle_get_recording", + "domain": "recordings", + "status": "mapped", + "id": "UI-182" + }, + { + "file": "web/js/components/preact/recordings/recordingsAPI.jsx", + "line": 204, + "url_literal": "/api/recordings?${params.toString()}", + "normalized": "/api/recordings", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/recordings", + "matched_handler": "handle_get_recordings", + "domain": "recordings", + "status": "mapped", + "id": "UI-183" + }, + { + "file": "web/js/components/preact/recordings/recordingsAPI.jsx", + "line": 227, + "url_literal": "/api/recordings/${recordingId}", + "normalized": "/api/recordings/{}", + "method": "DELETE", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "DELETE /api/recordings/#", + "matched_handler": "handle_delete_recording", + "domain": "recordings", + "status": "mapped", + "id": "UI-184" + }, + { + "file": "web/js/components/preact/recordings/recordingsAPI.jsx", + "line": 256, + "url_literal": "/api/recordings/batch-delete", + "normalized": "/api/recordings/batch-delete", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/recordings/batch-delete", + "matched_handler": "handle_batch_delete_recordings", + "domain": "recordings", + "status": "mapped", + "id": "UI-185" + }, + { + "file": "web/js/components/preact/recordings/recordingsAPI.jsx", + "line": 347, + "url_literal": "/api/recordings?${params.toString()}", + "normalized": "/api/recordings", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/recordings", + "matched_handler": "handle_get_recordings", + "domain": "recordings", + "status": "mapped", + "id": "UI-186" + }, + { + "file": "web/js/components/preact/recordings/recordingsAPI.jsx", + "line": 382, + "url_literal": "/api/recordings/${recording.id}", + "normalized": "/api/recordings/{}", + "method": "DELETE", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "DELETE /api/recordings/#", + "matched_handler": "handle_delete_recording", + "domain": "recordings", + "status": "mapped", + "id": "UI-187" + }, + { + "file": "web/js/components/preact/recordings/recordingsAPI.jsx", + "line": 435, + "url_literal": "/api/recordings/batch-delete", + "normalized": "/api/recordings/batch-delete", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/recordings/batch-delete", + "matched_handler": "handle_batch_delete_recordings", + "domain": "recordings", + "status": "mapped", + "id": "UI-188" + }, + { + "file": "web/js/components/preact/recordings/recordingsAPI.jsx", + "line": 472, + "url_literal": "/api/recordings/batch-delete/progress/${jobId}", + "normalized": "/api/recordings/batch-delete/progress/{}", + "method": "GET", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "GET /api/recordings/batch-delete/progress/#", + "matched_handler": "handle_batch_delete_progress", + "domain": "recordings", + "status": "mapped", + "id": "UI-189" + }, + { + "file": "web/js/components/preact/recordings/recordingsAPI.jsx", + "line": 546, + "url_literal": "/api/recordings?", + "normalized": "/api/recordings", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/recordings", + "matched_handler": "handle_get_recordings", + "domain": "recordings", + "status": "mapped", + "id": "UI-190" + }, + { + "file": "web/js/components/preact/recordings/recordingsAPI.jsx", + "line": 591, + "url_literal": "/api/recordings/batch-delete", + "normalized": "/api/recordings/batch-delete", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/recordings/batch-delete", + "matched_handler": "handle_batch_delete_recordings", + "domain": "recordings", + "status": "mapped", + "id": "UI-191" + }, + { + "file": "web/js/components/preact/recordings/recordingsAPI.jsx", + "line": 630, + "url_literal": "/api/detection/results/${recording.stream}?${params.toString()}", + "normalized": "/api/detection/results/{}", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/detection/results/#", + "matched_handler": "handle_get_detection_results", + "domain": "recordings", + "status": "mapped", + "id": "UI-192" + }, + { + "file": "web/js/components/preact/recordings/recordingsAPI.jsx", + "line": 661, + "url_literal": "/api/detection/results/${recording.stream}?${params.toString()}", + "normalized": "/api/detection/results/{}", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/detection/results/#", + "matched_handler": "handle_get_detection_results", + "domain": "recordings", + "status": "mapped", + "id": "UI-193" + }, + { + "file": "web/js/components/preact/recordings/recordingsAPI.jsx", + "line": 690, + "url_literal": "/api/recordings/play/${recording.id}${recording.external_source ? \"?archive=1\" : \"\"}", + "normalized": "/api/recordings/play/{}", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "media-or-binary", + "matched_route": "GET /api/recordings/play/#", + "matched_handler": "handle_recordings_playback", + "domain": "recordings", + "status": "mapped", + "id": "UI-194" + }, + { + "file": "web/js/components/preact/recordings/recordingsAPI.jsx", + "line": 692, + "url_literal": "/api/recordings/download/${recording.id}", + "normalized": "/api/recordings/download/{}", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "media-or-binary", + "matched_route": "GET /api/recordings/download/#", + "matched_handler": "handle_recordings_download", + "domain": "recordings", + "status": "mapped", + "id": "UI-195" + }, + { + "file": "web/js/components/preact/recordings/recordingsAPI.jsx", + "line": 715, + "url_literal": "/api/recordings/tags", + "normalized": "/api/recordings/tags", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/recordings/tags", + "matched_handler": "handle_get_recording_tags", + "domain": "recordings", + "status": "mapped", + "id": "UI-196" + }, + { + "file": "web/js/components/preact/recordings/recordingsAPI.jsx", + "line": 737, + "url_literal": "/api/recordings/detection-labels", + "normalized": "/api/recordings/detection-labels", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/recordings/detection-labels", + "matched_handler": "handle_get_recording_detection_labels", + "domain": "recordings", + "status": "mapped", + "id": "UI-197" + }, + { + "file": "web/js/components/preact/recordings/recordingsAPI.jsx", + "line": 756, + "url_literal": "/api/recordings/${recordingId}/tags", + "normalized": "/api/recordings/{}/tags", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/recordings/#/tags", + "matched_handler": "handle_get_recording_tags_by_id", + "domain": "recordings", + "status": "mapped", + "id": "UI-198" + }, + { + "file": "web/js/components/preact/recordings/recordingsAPI.jsx", + "line": 776, + "url_literal": "/api/recordings/${recordingId}/tags", + "normalized": "/api/recordings/{}/tags", + "method": "PUT", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "PUT /api/recordings/#/tags", + "matched_handler": "handle_put_recording_tags", + "domain": "recordings", + "status": "mapped", + "id": "UI-199" + }, + { + "file": "web/js/components/preact/recordings/recordingsAPI.jsx", + "line": 801, + "url_literal": "/api/recordings/batch-tags", + "normalized": "/api/recordings/batch-tags", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/recordings/batch-tags", + "matched_handler": "handle_batch_recording_tags", + "domain": "recordings", + "status": "mapped", + "id": "UI-200" + }, + { + "file": "web/js/components/preact/recordings/recordingsAPI.jsx", + "line": 822, + "url_literal": "/api/recordings/download/${recording.id}", + "normalized": "/api/recordings/download/{}", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "media-or-binary", + "matched_route": "GET /api/recordings/download/#", + "matched_handler": "handle_recordings_download", + "domain": "recordings", + "status": "mapped", + "id": "UI-201" + }, + { + "file": "web/js/components/preact/settings/GeneralTab.jsx", + "line": 190, + "url_literal": "/api/setup/status", + "normalized": "/api/setup/status", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/setup/status", + "matched_handler": "handle_post_setup_complete", + "domain": "settings", + "status": "mapped", + "id": "UI-202" + }, + { + "file": "web/js/components/preact/settings/Go2rtcTab.jsx", + "line": 112, + "url_literal": "/api/settings/go2rtc/validate", + "normalized": "/api/settings/go2rtc/validate", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/settings/go2rtc/validate", + "matched_handler": "handle_post_settings_go2rtc_validate", + "domain": "settings", + "status": "mapped", + "id": "UI-203" + }, + { + "file": "web/js/components/preact/settings/Go2rtcTab.jsx", + "line": 155, + "url_literal": "/api/system/go2rtc/effective-config", + "normalized": "/api/system/go2rtc/effective-config", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/system/go2rtc/effective-config", + "matched_handler": "handle_get_system_go2rtc_effective_config", + "domain": "settings", + "status": "mapped", + "id": "UI-204" + }, + { + "file": "web/js/components/preact/settings/Go2rtcTab.jsx", + "line": 156, + "url_literal": "/api/system/go2rtc/override-status", + "normalized": "/api/system/go2rtc/override-status", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/system/go2rtc/override-status", + "matched_handler": "handle_get_system_go2rtc_override_status", + "domain": "settings", + "status": "mapped", + "id": "UI-205" + }, + { + "file": "web/js/components/preact/settings/StorageTab.jsx", + "line": 191, + "url_literal": "/api/storage-targets", + "normalized": "/api/storage-targets", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/storage-targets", + "matched_handler": "handle_get_storage_targets", + "domain": "settings", + "status": "mapped", + "id": "UI-206" + }, + { + "file": "web/js/components/preact/settings/StorageTab.jsx", + "line": 195, + "url_literal": "/api/storage-policies", + "normalized": "/api/storage-policies", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/storage-policies", + "matched_handler": "handle_get_storage_policies", + "domain": "settings", + "status": "mapped", + "id": "UI-207" + }, + { + "file": "web/js/components/preact/settings/StorageTab.jsx", + "line": 196, + "url_literal": "/api/storage-pools", + "normalized": "/api/storage-pools", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/storage-pools", + "matched_handler": "handle_get_storage_pools", + "domain": "settings", + "status": "mapped", + "id": "UI-208" + }, + { + "file": "web/js/components/preact/settings/StorageTab.jsx", + "line": 237, + "url_literal": "/api/storage-targets/${encodeURIComponent(editor.uuid)}", + "normalized": "/api/storage-targets/{}", + "method": "PUT", + "method_evidence": "hand-reviewed ternary: method method: editor.uuid ? 'PUT' : 'POST' (URL #1 on line)", + "transport": "json", + "matched_route": "PUT /api/storage-targets/#", + "matched_handler": "handle_put_storage_target", + "domain": "settings", + "status": "mapped", + "id": "UI-209" + }, + { + "file": "web/js/components/preact/settings/StorageTab.jsx", + "line": 237, + "url_literal": "/api/storage-targets", + "normalized": "/api/storage-targets", + "method": "POST", + "method_evidence": "hand-reviewed ternary: method method: editor.uuid ? 'PUT' : 'POST' (URL #2 on line)", + "transport": "json", + "matched_route": "POST /api/storage-targets", + "matched_handler": "handle_post_storage_target", + "domain": "settings", + "status": "mapped", + "id": "UI-210" + }, + { + "file": "web/js/components/preact/settings/StorageTab.jsx", + "line": 257, + "url_literal": "/api/storage-targets/${encodeURIComponent(target.uuid)}/probe", + "normalized": "/api/storage-targets/{}/probe", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/storage-targets/#/probe", + "matched_handler": "handle_post_storage_target_probe", + "domain": "settings", + "status": "mapped", + "id": "UI-211" + }, + { + "file": "web/js/components/preact/settings/StorageTab.jsx", + "line": 277, + "url_literal": "/api/storage-targets/${encodeURIComponent(target.uuid)}?revision=${encodeURIComponent(target.revision)}", + "normalized": "/api/storage-targets/{}", + "method": "DELETE", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "DELETE /api/storage-targets/#", + "matched_handler": "handle_delete_storage_target", + "domain": "settings", + "status": "mapped", + "id": "UI-212" + }, + { + "file": "web/js/components/preact/settings/StorageTab.jsx", + "line": 357, + "url_literal": "/api/storage-pools", + "normalized": "/api/storage-pools", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/storage-pools", + "matched_handler": "handle_get_storage_pools", + "domain": "settings", + "status": "mapped", + "id": "UI-213" + }, + { + "file": "web/js/components/preact/settings/StorageTab.jsx", + "line": 358, + "url_literal": "/api/storage-targets", + "normalized": "/api/storage-targets", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/storage-targets", + "matched_handler": "handle_get_storage_targets", + "domain": "settings", + "status": "mapped", + "id": "UI-214" + }, + { + "file": "web/js/components/preact/settings/StorageTab.jsx", + "line": 373, + "url_literal": "/api/storage-pools/${encodeURIComponent(editor.uuid)}", + "normalized": "/api/storage-pools/{}", + "method": "PUT", + "method_evidence": "hand-reviewed ternary: method method: editor.uuid ? 'PUT' : 'POST' (URL #1 on line)", + "transport": "json", + "matched_route": "PUT /api/storage-pools/#", + "matched_handler": "handle_put_storage_pool", + "domain": "settings", + "status": "mapped", + "id": "UI-215" + }, + { + "file": "web/js/components/preact/settings/StorageTab.jsx", + "line": 373, + "url_literal": "/api/storage-pools", + "normalized": "/api/storage-pools", + "method": "POST", + "method_evidence": "hand-reviewed ternary: method method: editor.uuid ? 'PUT' : 'POST' (URL #2 on line)", + "transport": "json", + "matched_route": "POST /api/storage-pools", + "matched_handler": "handle_post_storage_pool", + "domain": "settings", + "status": "mapped", + "id": "UI-216" + }, + { + "file": "web/js/components/preact/settings/StorageTab.jsx", + "line": 382, + "url_literal": "/api/storage-pools/${encodeURIComponent(pool.uuid)}?revision=${encodeURIComponent(pool.revision)}", + "normalized": "/api/storage-pools/{}", + "method": "DELETE", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "DELETE /api/storage-pools/#", + "matched_handler": "handle_delete_storage_pool", + "domain": "settings", + "status": "mapped", + "id": "UI-217" + }, + { + "file": "web/js/components/preact/settings/StorageTab.jsx", + "line": 407, + "url_literal": "/api/storage-archive", + "normalized": "/api/storage-archive", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/storage-archive", + "matched_handler": "handle_get_storage_archive", + "domain": "settings", + "status": "mapped", + "id": "UI-218" + }, + { + "file": "web/js/components/preact/settings/StorageTab.jsx", + "line": 414, + "url_literal": "/api/storage-archive/retry", + "normalized": "/api/storage-archive/retry", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/storage-archive/retry", + "matched_handler": "handle_post_storage_archive_retry", + "domain": "settings", + "status": "mapped", + "id": "UI-219" + }, + { + "file": "web/js/components/preact/settings/StorageTab.jsx", + "line": 444, + "url_literal": "/api/storage-migrations", + "normalized": "/api/storage-migrations", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/storage-migrations", + "matched_handler": "handle_get_storage_migrations", + "domain": "settings", + "status": "mapped", + "id": "UI-220" + }, + { + "file": "web/js/components/preact/settings/StorageTab.jsx", + "line": 453, + "url_literal": "/api/storage-migrations/${encodeURIComponent(job.uuid)}/${action}", + "normalized": "/api/storage-migrations/{}/cancel", + "method": "POST", + "method_evidence": "hand-reviewed: actOnJob(job,'cancel')", + "transport": "json", + "matched_route": "POST /api/storage-migrations/#/cancel", + "matched_handler": "handle_post_storage_migration_cancel", + "domain": "settings", + "status": "mapped", + "id": "UI-221" + }, + { + "file": "web/js/components/preact/settings/StorageTab.jsx", + "line": 453, + "url_literal": "/api/storage-migrations/${encodeURIComponent(job.uuid)}/${action}", + "normalized": "/api/storage-migrations/{}/retry", + "method": "POST", + "method_evidence": "hand-reviewed: actOnJob(job,'retry')", + "transport": "json", + "matched_route": "POST /api/storage-migrations/#/retry", + "matched_handler": "handle_post_storage_migration_retry", + "domain": "settings", + "status": "mapped", + "id": "UI-222" + }, + { + "file": "web/js/components/preact/settings/StorageTab.jsx", + "line": 557, + "url_literal": "/api/storage-policies", + "normalized": "/api/storage-policies", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/storage-policies", + "matched_handler": "handle_get_storage_policies", + "domain": "settings", + "status": "mapped", + "id": "UI-223" + }, + { + "file": "web/js/components/preact/settings/StorageTab.jsx", + "line": 558, + "url_literal": "/api/storage-targets", + "normalized": "/api/storage-targets", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/storage-targets", + "matched_handler": "handle_get_storage_targets", + "domain": "settings", + "status": "mapped", + "id": "UI-224" + }, + { + "file": "web/js/components/preact/settings/StorageTab.jsx", + "line": 559, + "url_literal": "/api/storage-pools", + "normalized": "/api/storage-pools", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/storage-pools", + "matched_handler": "handle_get_storage_pools", + "domain": "settings", + "status": "mapped", + "id": "UI-225" + }, + { + "file": "web/js/components/preact/settings/StorageTab.jsx", + "line": 560, + "url_literal": "/api/locations", + "normalized": "/api/locations", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/locations", + "matched_handler": "handle_get_locations", + "domain": "settings", + "status": "mapped", + "id": "UI-226" + }, + { + "file": "web/js/components/preact/settings/StorageTab.jsx", + "line": 561, + "url_literal": "/api/camera-tags", + "normalized": "/api/camera-tags", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/camera-tags", + "matched_handler": "handle_get_camera_tags", + "domain": "settings", + "status": "mapped", + "id": "UI-227" + }, + { + "file": "web/js/components/preact/settings/StorageTab.jsx", + "line": 569, + "url_literal": "/api/storage-policies/${policy.uuid}/apply", + "normalized": "/api/storage-policies/{}/apply", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/storage-policies/#/apply", + "matched_handler": "handle_post_storage_policy_apply", + "domain": "settings", + "status": "mapped", + "id": "UI-228" + }, + { + "file": "web/js/components/preact/settings/StorageTab.jsx", + "line": 617, + "url_literal": "/api/storage-policies/preview", + "normalized": "/api/storage-policies/preview", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/storage-policies/preview", + "matched_handler": "handle_post_storage_policy_preview", + "domain": "settings", + "status": "mapped", + "id": "UI-229" + }, + { + "file": "web/js/components/preact/settings/StorageTab.jsx", + "line": 630, + "url_literal": "/api/storage-policies/${encodeURIComponent(editor.uuid)}", + "normalized": "/api/storage-policies/{}", + "method": "PUT", + "method_evidence": "hand-reviewed ternary: method method: editor.uuid ? 'PUT' : 'POST' (URL #1 on line)", + "transport": "json", + "matched_route": "PUT /api/storage-policies/#", + "matched_handler": "handle_put_storage_policy", + "domain": "settings", + "status": "mapped", + "id": "UI-230" + }, + { + "file": "web/js/components/preact/settings/StorageTab.jsx", + "line": 630, + "url_literal": "/api/storage-policies", + "normalized": "/api/storage-policies", + "method": "POST", + "method_evidence": "hand-reviewed ternary: method method: editor.uuid ? 'PUT' : 'POST' (URL #2 on line)", + "transport": "json", + "matched_route": "POST /api/storage-policies", + "matched_handler": "handle_post_storage_policy", + "domain": "settings", + "status": "mapped", + "id": "UI-231" + }, + { + "file": "web/js/components/preact/settings/StorageTab.jsx", + "line": 642, + "url_literal": "/api/storage-policies/${encodeURIComponent(policy.uuid)}?revision=${encodeURIComponent(policy.revision)}", + "normalized": "/api/storage-policies/{}", + "method": "DELETE", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "DELETE /api/storage-policies/#", + "matched_handler": "handle_delete_storage_policy", + "domain": "settings", + "status": "mapped", + "id": "UI-232" + }, + { + "file": "web/js/components/preact/settings/StorageTab.jsx", + "line": 672, + "url_literal": "/api/storage-compliance", + "normalized": "/api/storage-compliance", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/storage-compliance", + "matched_handler": "handle_get_storage_compliance", + "domain": "settings", + "status": "mapped", + "id": "UI-233" + }, + { + "file": "web/js/components/preact/settings/WorkspacesTab.jsx", + "line": 24, + "url_literal": "/api/ui/workspaces", + "normalized": "/api/ui/workspaces", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/ui/workspaces", + "matched_handler": "handle_get_ui_workspaces", + "domain": "settings", + "status": "mapped", + "id": "UI-234" + }, + { + "file": "web/js/components/preact/settings/WorkspacesTab.jsx", + "line": 35, + "url_literal": "/api/ui/workspaces", + "normalized": "/api/ui/workspaces", + "method": "PUT", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "PUT /api/ui/workspaces", + "matched_handler": "handle_put_ui_workspaces", + "domain": "settings", + "status": "mapped", + "id": "UI-235" + }, + { + "file": "web/js/components/preact/system/HealthIncidentHistory.jsx", + "line": 10, + "url_literal": "/api/system/health/incidents?limit=${HISTORY_PAGE_SIZE}&include_closed=true${suffix}", + "normalized": "/api/system/health/incidents", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/system/health/incidents", + "matched_handler": "handle_get_system_health_incidents", + "domain": "system-health", + "status": "mapped", + "id": "UI-236" + }, + { + "file": "web/js/components/preact/system/LogsPoller.jsx", + "line": 57, + "url_literal": "/api/system/logs?level=${encodeURIComponent(normalizedLevel)}&count=${encodeURIComponent(logCount)}", + "normalized": "/api/system/logs", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/system/logs", + "matched_handler": "handle_get_system_logs", + "domain": "system-health", + "status": "mapped", + "id": "UI-237" + }, + { + "file": "web/js/components/preact/system/RestartModal.jsx", + "line": 54, + "url_literal": "/api/system/status", + "normalized": "/api/system/status", + "method": "GET", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "GET /api/system/status", + "matched_handler": "handle_get_system_status", + "domain": "system-health", + "status": "mapped", + "id": "UI-238" + }, + { + "file": "web/js/components/preact/system/RestartModal.jsx", + "line": 90, + "url_literal": "/api/system/status", + "normalized": "/api/system/status", + "method": "GET", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "GET /api/system/status", + "matched_handler": "handle_get_system_status", + "domain": "system-health", + "status": "mapped", + "id": "UI-239" + }, + { + "file": "web/js/components/preact/system/StorageHealth.jsx", + "line": 60, + "url_literal": "/api/storage/health", + "normalized": "/api/storage/health", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/storage/health", + "matched_handler": "handle_get_storage_health", + "domain": "system-health", + "status": "mapped", + "id": "UI-240" + }, + { + "file": "web/js/components/preact/system/StorageHealth.jsx", + "line": 67, + "url_literal": "/api/storage/cleanup", + "normalized": "/api/storage/cleanup", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/storage/cleanup", + "matched_handler": "handle_post_storage_cleanup", + "domain": "system-health", + "status": "mapped", + "id": "UI-241" + }, + { + "file": "web/js/components/preact/system/SystemHealth.jsx", + "line": 184, + "url_literal": "/api/system/health", + "normalized": "/api/system/health", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/system/health", + "matched_handler": "handle_get_system_health", + "domain": "system-health", + "status": "mapped", + "id": "UI-242" + }, + { + "file": "web/js/components/preact/system/WebServiceInfo.jsx", + "line": 34, + "url_literal": "/api/health", + "normalized": "/api/health", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/health", + "matched_handler": "handle_get_health", + "domain": "system-health", + "status": "mapped", + "id": "UI-243" + }, + { + "file": "web/js/components/preact/timeline/TimelineControls.jsx", + "line": 245, + "url_literal": "/api/recordings/${currentRecordingId}/protect", + "normalized": "/api/recordings/{}/protect", + "method": "PUT", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "PUT /api/recordings/#/protect", + "matched_handler": "handle_put_recording_protect", + "domain": "timeline", + "status": "mapped", + "id": "UI-244" + }, + { + "file": "web/js/components/preact/timeline/TimelineCursor.jsx", + "line": 73, + "url_literal": "/api/recordings/thumbnail/${encodeURIComponent(segment.id)}/${frameIndex}", + "normalized": "/api/recordings/thumbnail/{}/{}", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "media-or-binary", + "matched_route": "GET /api/recordings/thumbnail/#/#", + "matched_handler": "handle_recordings_thumbnail", + "domain": "timeline", + "status": "mapped", + "id": "UI-245" + }, + { + "file": "web/js/components/preact/timeline/TimelineCursor.jsx", + "line": 444, + "url_literal": "/api/recordings/thumbnail/${encodeURIComponent(current.recordingId)}/0", + "normalized": "/api/recordings/thumbnail/{}/0", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "media-or-binary", + "matched_route": "GET /api/recordings/thumbnail/#/#", + "matched_handler": "handle_recordings_thumbnail", + "domain": "timeline", + "status": "mapped", + "id": "UI-246" + }, + { + "file": "web/js/components/preact/timeline/TimelinePage.jsx", + "line": 1094, + "url_literal": "/api/timeline/segments-by-ids?ids=${encodeURIComponent(recordingIds)}", + "normalized": "/api/timeline/segments-by-ids", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/timeline/segments-by-ids", + "matched_handler": "handle_get_timeline_segments_by_ids", + "domain": "timeline", + "status": "mapped", + "id": "UI-247" + }, + { + "file": "web/js/components/preact/timeline/TimelinePlayer.jsx", + "line": 291, + "url_literal": "/api/recordings/play/${segment.id}?v=${encodeURIComponent(segmentVersion)}", + "normalized": "/api/recordings/play/{}", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "media-or-binary", + "matched_route": "GET /api/recordings/play/#", + "matched_handler": "handle_recordings_playback", + "domain": "timeline", + "status": "mapped", + "id": "UI-248" + }, + { + "file": "web/js/components/preact/timeline/TimelinePlayer.jsx", + "line": 575, + "url_literal": "/api/recordings/${segment.id}", + "normalized": "/api/recordings/{}", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/recordings/#", + "matched_handler": "handle_get_recording", + "domain": "timeline", + "status": "mapped", + "id": "UI-249" + }, + { + "file": "web/js/components/preact/timeline/TimelinePlayer.jsx", + "line": 612, + "url_literal": "/api/recordings/${segment.id}/tags", + "normalized": "/api/recordings/{}/tags", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/recordings/#/tags", + "matched_handler": "handle_get_recording_tags_by_id", + "domain": "timeline", + "status": "mapped", + "id": "UI-250" + }, + { + "file": "web/js/components/preact/timeline/TimelinePlayer.jsx", + "line": 629, + "url_literal": "/api/detection/results/${encodeURIComponent(data.stream)}?start=${startTime}&end=${endTime}", + "normalized": "/api/detection/results/{}", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/detection/results/#", + "matched_handler": "handle_get_detection_results", + "domain": "timeline", + "status": "mapped", + "id": "UI-251" + }, + { + "file": "web/js/components/preact/timeline/TimelinePlayer.jsx", + "line": 847, + "url_literal": "/api/recordings/${currentSegmentId}", + "normalized": "/api/recordings/{}", + "method": "DELETE", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "DELETE /api/recordings/#", + "matched_handler": "handle_delete_recording", + "domain": "timeline", + "status": "mapped", + "id": "UI-252" + }, + { + "file": "web/js/components/preact/timeline/TimelinePlayer.jsx", + "line": 1075, + "url_literal": "/api/recordings/download/${currentSegmentId}", + "normalized": "/api/recordings/download/{}", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "media-or-binary", + "matched_route": "GET /api/recordings/download/#", + "matched_handler": "handle_recordings_download", + "domain": "timeline", + "status": "mapped", + "id": "UI-253" + }, + { + "file": "web/js/components/preact/timeline/TimelineSegments.jsx", + "line": 214, + "url_literal": "/api/recordings/thumbnail/${encodeURIComponent(segment.id)}/${frameIndex}", + "normalized": "/api/recordings/thumbnail/{}/{}", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "media-or-binary", + "matched_route": "GET /api/recordings/thumbnail/#/#", + "matched_handler": "handle_recordings_thumbnail", + "domain": "timeline", + "status": "mapped", + "id": "UI-254" + }, + { + "file": "web/js/components/preact/timeline/TimelineSegments.jsx", + "line": 352, + "url_literal": "/api/recordings/thumbnail/${encodeURIComponent(current.recordingId)}/0", + "normalized": "/api/recordings/thumbnail/{}/0", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "media-or-binary", + "matched_route": "GET /api/recordings/thumbnail/#/#", + "matched_handler": "handle_recordings_thumbnail", + "domain": "timeline", + "status": "mapped", + "id": "UI-255" + }, + { + "file": "web/js/components/preact/timeline/timelineUtils.js", + "line": 22, + "url_literal": "/api/timeline/segments?${params.toString()}", + "normalized": "/api/timeline/segments", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/timeline/segments", + "matched_handler": "handle_get_timeline_segments", + "domain": "timeline", + "status": "mapped", + "id": "UI-256" + }, + { + "file": "web/js/components/preact/users/AccessPolicyModal.jsx", + "line": 110, + "url_literal": "/api/authorization/simulate", + "normalized": "/api/authorization/simulate", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/authorization/simulate", + "matched_handler": "handle_post_authorization_simulate", + "domain": "users-access", + "status": "mapped", + "id": "UI-257" + }, + { + "file": "web/js/components/preact/users/AccessPolicyModal.jsx", + "line": 182, + "url_literal": "/api/authorization/actions", + "normalized": "/api/authorization/actions", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/authorization/actions", + "matched_handler": "handle_get_authorization_actions", + "domain": "users-access", + "status": "mapped", + "id": "UI-258" + }, + { + "file": "web/js/components/preact/users/AccessPolicyModal.jsx", + "line": 183, + "url_literal": "/api/authorization/roles", + "normalized": "/api/authorization/roles", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/authorization/roles", + "matched_handler": "handle_get_authorization_roles", + "domain": "users-access", + "status": "mapped", + "id": "UI-259" + }, + { + "file": "web/js/components/preact/users/AccessPolicyModal.jsx", + "line": 184, + "url_literal": "/api/authorization/users/${encodeURIComponent(user.id)}", + "normalized": "/api/authorization/users/{}", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/authorization/users/#", + "matched_handler": "handle_get_user_authorization", + "domain": "users-access", + "status": "mapped", + "id": "UI-260" + }, + { + "file": "web/js/components/preact/users/AccessPolicyModal.jsx", + "line": 185, + "url_literal": "/api/camera-collections", + "normalized": "/api/camera-collections", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/camera-collections", + "matched_handler": "handle_get_camera_collections", + "domain": "users-access", + "status": "mapped", + "id": "UI-261" + }, + { + "file": "web/js/components/preact/users/AccessPolicyModal.jsx", + "line": 186, + "url_literal": "/api/locations", + "normalized": "/api/locations", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/locations", + "matched_handler": "handle_get_locations", + "domain": "users-access", + "status": "mapped", + "id": "UI-262" + }, + { + "file": "web/js/components/preact/users/AccessPolicyModal.jsx", + "line": 187, + "url_literal": "/api/camera-tags", + "normalized": "/api/camera-tags", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/camera-tags", + "matched_handler": "handle_get_camera_tags", + "domain": "users-access", + "status": "mapped", + "id": "UI-263" + }, + { + "file": "web/js/components/preact/users/AccessPolicyModal.jsx", + "line": 246, + "url_literal": "/api/authorization/users/${encodeURIComponent(user.id)}", + "normalized": "/api/authorization/users/{}", + "method": "PUT", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "PUT /api/authorization/users/#", + "matched_handler": "handle_put_user_authorization", + "domain": "users-access", + "status": "mapped", + "id": "UI-264" + }, + { + "file": "web/js/components/preact/users/ApiKeyModal.jsx", + "line": 146, + "url_literal": "/api/authorization/actions", + "normalized": "/api/authorization/actions", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/authorization/actions", + "matched_handler": "handle_get_authorization_actions", + "domain": "users-access", + "status": "mapped", + "id": "UI-265" + }, + { + "file": "web/js/components/preact/users/ApiKeyModal.jsx", + "line": 147, + "url_literal": "/api/authorization/users/${encodeURIComponent(currentUser.id)}/tokens", + "normalized": "/api/authorization/users/{}/tokens", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/authorization/users/#/tokens", + "matched_handler": "handle_get_user_api_tokens", + "domain": "users-access", + "status": "mapped", + "id": "UI-266" + }, + { + "file": "web/js/components/preact/users/ApiKeyModal.jsx", + "line": 148, + "url_literal": "/api/camera-collections", + "normalized": "/api/camera-collections", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/camera-collections", + "matched_handler": "handle_get_camera_collections", + "domain": "users-access", + "status": "mapped", + "id": "UI-267" + }, + { + "file": "web/js/components/preact/users/ApiKeyModal.jsx", + "line": 149, + "url_literal": "/api/locations", + "normalized": "/api/locations", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/locations", + "matched_handler": "handle_get_locations", + "domain": "users-access", + "status": "mapped", + "id": "UI-268" + }, + { + "file": "web/js/components/preact/users/ApiKeyModal.jsx", + "line": 150, + "url_literal": "/api/camera-tags", + "normalized": "/api/camera-tags", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/camera-tags", + "matched_handler": "handle_get_camera_tags", + "domain": "users-access", + "status": "mapped", + "id": "UI-269" + }, + { + "file": "web/js/components/preact/users/ApiKeyModal.jsx", + "line": 196, + "url_literal": "/api/authorization/users/${encodeURIComponent(currentUser.id)}/tokens", + "normalized": "/api/authorization/users/{}/tokens", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/authorization/users/#/tokens", + "matched_handler": "handle_post_user_api_token", + "domain": "users-access", + "status": "mapped", + "id": "UI-270" + }, + { + "file": "web/js/components/preact/users/ApiKeyModal.jsx", + "line": 223, + "url_literal": "/api/authorization/users/${encodeURIComponent(currentUser.id)}/tokens/${encodeURIComponent(token.uuid)}", + "normalized": "/api/authorization/users/{}/tokens/{}", + "method": "DELETE", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "DELETE /api/authorization/users/#/tokens/#", + "matched_handler": "handle_delete_user_api_token", + "domain": "users-access", + "status": "mapped", + "id": "UI-271" + }, + { + "file": "web/js/components/preact/users/ApiKeyModal.jsx", + "line": 242, + "url_literal": "/api/auth/users/${encodeURIComponent(currentUser.id)}/api-key", + "normalized": "/api/auth/users/{}/api-key", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/auth/users/#/api-key", + "matched_handler": "handle_users_generate_api_key", + "domain": "users-access", + "status": "mapped", + "id": "UI-272" + }, + { + "file": "web/js/components/preact/users/AuditDecisionModes.jsx", + "line": 31, + "url_literal": "/api/audit/settings", + "normalized": "/api/audit/settings", + "method": "PUT", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "PUT /api/audit/settings", + "matched_handler": "handle_put_audit_settings", + "domain": "users-access", + "status": "mapped", + "id": "UI-273" + }, + { + "file": "web/js/components/preact/users/AuditHistoryModal.jsx", + "line": 103, + "url_literal": "/api/audit/events?${queryString}", + "normalized": "/api/audit/events", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/audit/events", + "matched_handler": "handle_get_audit_events", + "domain": "users-access", + "status": "mapped", + "id": "UI-274" + }, + { + "file": "web/js/components/preact/users/AuditHistoryModal.jsx", + "line": 121, + "url_literal": "/api/audit/settings", + "normalized": "/api/audit/settings", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/audit/settings", + "matched_handler": "handle_get_audit_settings", + "domain": "users-access", + "status": "mapped", + "id": "UI-275" + }, + { + "file": "web/js/components/preact/users/AuditHistoryModal.jsx", + "line": 176, + "url_literal": "/api/audit/settings", + "normalized": "/api/audit/settings", + "method": "PUT", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "PUT /api/audit/settings", + "matched_handler": "handle_put_audit_settings", + "domain": "users-access", + "status": "mapped", + "id": "UI-276" + }, + { + "file": "web/js/components/preact/users/AuditHistoryModal.jsx", + "line": 198, + "url_literal": "/api/audit/events/export?${queryString}", + "normalized": "/api/audit/events/export", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/audit/events/export", + "matched_handler": "handle_get_audit_export", + "domain": "users-access", + "status": "mapped", + "id": "UI-277" + }, + { + "file": "web/js/components/preact/users/RoleManagerModal.jsx", + "line": 41, + "url_literal": "/api/authorization/actions", + "normalized": "/api/authorization/actions", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/authorization/actions", + "matched_handler": "handle_get_authorization_actions", + "domain": "users-access", + "status": "mapped", + "id": "UI-278" + }, + { + "file": "web/js/components/preact/users/RoleManagerModal.jsx", + "line": 42, + "url_literal": "/api/authorization/roles", + "normalized": "/api/authorization/roles", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/authorization/roles", + "matched_handler": "handle_get_authorization_roles", + "domain": "users-access", + "status": "mapped", + "id": "UI-279" + }, + { + "file": "web/js/components/preact/users/RoleManagerModal.jsx", + "line": 101, + "url_literal": "/api/authorization/roles/${encodeURIComponent(draft.uuid)}", + "normalized": "/api/authorization/roles/{}", + "method": "PUT", + "method_evidence": "hand-reviewed ternary: method method: draft.uuid ? 'PUT' : 'POST' (URL #1 on line)", + "transport": "json", + "matched_route": "PUT /api/authorization/roles/#", + "matched_handler": "handle_put_authorization_role", + "domain": "users-access", + "status": "mapped", + "id": "UI-280" + }, + { + "file": "web/js/components/preact/users/RoleManagerModal.jsx", + "line": 101, + "url_literal": "/api/authorization/roles", + "normalized": "/api/authorization/roles", + "method": "POST", + "method_evidence": "hand-reviewed ternary: method method: draft.uuid ? 'PUT' : 'POST' (URL #2 on line)", + "transport": "json", + "matched_route": "POST /api/authorization/roles", + "matched_handler": "handle_post_authorization_role", + "domain": "users-access", + "status": "mapped", + "id": "UI-281" + }, + { + "file": "web/js/components/preact/users/RoleManagerModal.jsx", + "line": 129, + "url_literal": "/api/authorization/roles/${encodeURIComponent(draft.uuid)}", + "normalized": "/api/authorization/roles/{}", + "method": "DELETE", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "DELETE /api/authorization/roles/#", + "matched_handler": "handle_delete_authorization_role", + "domain": "users-access", + "status": "mapped", + "id": "UI-282" + }, + { + "file": "web/js/components/preact/users/TotpSetupModal.jsx", + "line": 33, + "url_literal": "/api/auth/users/${user.id}/totp/status", + "normalized": "/api/auth/users/{}/totp/status", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/auth/users/#/totp/status", + "matched_handler": "handle_totp_status", + "domain": "users-access", + "status": "mapped", + "id": "UI-283" + }, + { + "file": "web/js/components/preact/users/TotpSetupModal.jsx", + "line": 70, + "url_literal": "/api/auth/users/${user.id}/totp/setup", + "normalized": "/api/auth/users/{}/totp/setup", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/auth/users/#/totp/setup", + "matched_handler": "handle_totp_setup", + "domain": "users-access", + "status": "mapped", + "id": "UI-284" + }, + { + "file": "web/js/components/preact/users/TotpSetupModal.jsx", + "line": 96, + "url_literal": "/api/auth/users/${user.id}/totp/verify", + "normalized": "/api/auth/users/{}/totp/verify", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/auth/users/#/totp/verify", + "matched_handler": "handle_totp_verify", + "domain": "users-access", + "status": "mapped", + "id": "UI-285" + }, + { + "file": "web/js/components/preact/users/TotpSetupModal.jsx", + "line": 117, + "url_literal": "/api/auth/users/${user.id}/totp/disable", + "normalized": "/api/auth/users/{}/totp/disable", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/auth/users/#/totp/disable", + "matched_handler": "handle_totp_disable", + "domain": "users-access", + "status": "mapped", + "id": "UI-286" + }, + { + "file": "web/js/pages/hls-page.jsx", + "line": 36, + "url_literal": "/api/client-config", + "normalized": "/api/client-config", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/client-config", + "matched_handler": "handle_get_client_config", + "domain": "live", + "status": "mapped", + "id": "UI-287" + }, + { + "file": "web/js/pages/index-page.jsx", + "line": 40, + "url_literal": "/api/client-config", + "normalized": "/api/client-config", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/client-config", + "matched_handler": "handle_get_client_config", + "domain": "live", + "status": "mapped", + "id": "UI-288" + }, + { + "file": "web/js/pages/index-page.jsx", + "line": 41, + "url_literal": "/api/setup/status", + "normalized": "/api/setup/status", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/setup/status", + "matched_handler": "handle_get_setup_status", + "domain": "live", + "status": "mapped", + "id": "UI-289" + }, + { + "file": "web/js/utils/auth-utils.js", + "line": 48, + "url_literal": "/api/auth/verify", + "normalized": "/api/auth/verify", + "method": "GET", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "GET /api/auth/verify", + "matched_handler": "handle_auth_verify", + "domain": "shared-auth", + "status": "mapped", + "id": "UI-290" + }, + { + "file": "web/js/utils/detection-polling.js", + "line": 25, + "url_literal": "/api/detection/results/${encodeURIComponent(streamName)}", + "normalized": "/api/detection/results/{}", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/detection/results/#", + "matched_handler": "handle_get_detection_results", + "domain": "live", + "status": "mapped", + "id": "UI-291" + }, + { + "file": "web/js/utils/player-telemetry.js", + "line": 37, + "url_literal": "/api/telemetry/player", + "normalized": "/api/telemetry/player", + "method": "POST", + "method_evidence": "hand-reviewed: navigator.sendBeacon (POST)", + "transport": "json", + "matched_route": "POST /api/telemetry/player", + "matched_handler": "handle_post_player_telemetry", + "domain": "live", + "status": "mapped", + "id": "UI-292" + }, + { + "file": "web/js/utils/player-telemetry.js", + "line": 39, + "url_literal": "/api/telemetry/player", + "normalized": "/api/telemetry/player", + "method": "POST", + "method_evidence": "explicit method option", + "transport": "json", + "matched_route": "POST /api/telemetry/player", + "matched_handler": "handle_post_player_telemetry", + "domain": "live", + "status": "mapped", + "id": "UI-293" + }, + { + "file": "web/js/utils/settings-utils.js", + "line": 37, + "url_literal": "/api/client-config", + "normalized": "/api/client-config", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/client-config", + "matched_handler": "handle_get_client_config", + "domain": "shared-shell", + "status": "mapped", + "id": "UI-294" + }, + { + "file": "web/js/utils/stream-summaries.js", + "line": 24, + "url_literal": "/api/streams?${params.toString()}", + "normalized": "/api/streams", + "method": "GET", + "method_evidence": "default (no method option / resource URL)", + "transport": "json", + "matched_route": "GET /api/streams", + "matched_handler": "handle_get_streams", + "domain": "streams", + "status": "mapped", + "id": "UI-295" + }, + { + "file": "web/js/components/preact/HLSVideoCell.jsx", + "line": 245, + "url_literal": "(hls.js follows m3u8 playlist)", + "normalized": "/go2rtc/api/hls/{}", + "method": "GET", + "method_evidence": "hand-reviewed: hls.js fetches segment/playlist URLs referenced by /go2rtc/api/stream.m3u8", + "transport": "go2rtc-proxy", + "matched_route": "GET /go2rtc/api/hls/*", + "matched_handler": "handle_go2rtc_proxy", + "domain": "live", + "status": "mapped", + "derived": true, + "id": "UI-296" + }, + { + "file": "web/js/components/preact/HLSVideoCell.jsx", + "line": 206, + "url_literal": "(hls.js follows /hls/{name}/index.m3u8 segments)", + "normalized": "/hls/{}/{}", + "method": "GET", + "method_evidence": "hand-reviewed: native HLS segment fetches served by handle_direct_hls_request", + "transport": "native-hls-media", + "matched_route": "GET /hls/#/#", + "matched_handler": "handle_direct_hls_request", + "domain": "live", + "status": "mapped", + "derived": true, + "id": "UI-297" + } + ], + "summary": { + "routes": 239, + "routes_with_ui_consumer": 190, + "routes_without_ui_consumer": [ + "GET /api/health/hls", + "GET /api/metrics", + "POST /api/motion/trigger", + "GET /api/locations/#", + "GET /api/live/plans/#", + "GET /api/camera-tags/#", + "GET /api/fleet/views/#", + "PUT /api/fleet/views/#", + "PUT /api/live/layouts/#", + "GET /api/camera-collections/#", + "POST /api/streams/#/recording", + "DELETE /api/streams/#/zones", + "GET /api/streams/#/retention", + "PUT /api/streams/#/retention", + "GET /api/streams/#/detection-engines", + "PUT /api/streams/#/detection-engines", + "POST /api/streams/#/ptz/absolute", + "POST /api/streams/#/ptz/relative", + "GET /api/streams/#/imaging/settings", + "PUT /api/streams/#/imaging/settings", + "GET /api/streams/#/imaging/options", + "GET /api/streams/#", + "GET /api/system", + "POST /api/system/shutdown", + "POST /api/system/backup", + "GET /api/snapshots/#/#", + "GET /api/auth/users/#", + "PUT /api/auth/users/#/password-lock", + "GET /api/event-destinations/#", + "GET /api/storage-targets/#", + "POST /api/storage-migrations", + "GET /api/storage-migrations/#", + "GET /api/storage-pools/#", + "GET /api/storage-policies/#", + "GET /api/event-routes/#", + "GET /api/onvif/discovery/status", + "POST /api/onvif/device/add", + "GET /api/recordings/files/check", + "DELETE /api/recordings/files", + "GET /api/recordings/protected", + "POST /api/recordings/sync", + "PUT /api/recordings/#/retention", + "GET /api/timeline/manifest", + "GET /api/timeline/play", + "GET /api/investigations/thumbnail/#/#", + "GET /api/investigation-bookmarks/#", + "PUT /api/investigation-bookmarks/#", + "GET /go2rtc/api/streams", + "POST /go2rtc/api/reload" + ], + "ui_operations": 297, + "ui_operations_mapped": 297, + "ui_operation_discrepancies": 0, + "distinct_ui_method_routes": 190, + "discrepancies_total": 10 + }, + "discrepancies": [ + { + "id": "D-01", + "kind": "semantic", + "where": "web/js/components/preact/StreamsView.jsx:690", + "detail": "PUT /api/streams/{name} sends is_deleted:false (\"undelete\") but handle_put_stream never reads is_deleted; the field is silently ignored." + }, + { + "id": "D-02", + "kind": "semantic", + "where": "web/js/components/preact/StreamsView.jsx:832", + "detail": "Edit modal reads stream.detection_zones from GET /api/streams/{name}/full but the handler never emits it; zones come only from GET /api/streams/{name}/zones (StreamConfigModal.jsx:336). Comment on handle_get_stream_full (\"also returns motion recording config\") is stale: response is {stream:{...}} only." + }, + { + "id": "D-03", + "kind": "semantic", + "where": "web/js/components/preact/StreamCard.jsx:319", + "detail": "StreamCard reads stream.error_message and the playback transport, but the admin summary (surface=admin) emits neither error_message nor playback_transport (playback_transport only on surface=live). Card tooltip/transport label silently fall back." + }, + { + "id": "D-04", + "kind": "policy", + "where": "web/js/components/preact/StreamsView.jsx:221", + "detail": "StreamsView always queries GET /api/detection/models; handler returns 403 for principals with no camera_configure grant (e.g. viewers). Contract must model 403 for this option dependency; UI tolerates it by showing no models." + }, + { + "id": "D-05", + "kind": "policy", + "where": "web/js/components/preact/StreamsView.jsx:78", + "detail": "Edit/delete gating mixes legacy role names from /api/auth/verify (admin/user/viewer) with per-camera summary can_configure; server authority is AUTHZ_CAMERA_CONFIGURE per camera. Edit-modal credential hiding uses /full can_control_privacy (== configure decision) as proxy." + }, + { + "id": "D-06", + "kind": "client-quirk", + "where": "web/js/pages/index-page.jsx:52", + "detail": "/api/client-config returns snake_case only; index-page reads camelCase first (dead branches)." + }, + { + "id": "D-07", + "kind": "routing", + "where": "src/web/libuv_connection.c:on_message_complete", + "detail": "Router is first-match in registration order with method filter, \"#\" = one possibly-empty run of non-\"/\" characters, \"*\" = rest. Unmatched paths fall to static file resolution (returns 404 JSON for unknown /api/* paths rather than 405 for wrong method). Entire /go2rtc and /go2rtc/* namespace is diverted to the proxy thread regardless of method/registration (only 7 go2rtc patterns are registered; the proxy authorizer enforces its own allow-list)." + }, + { + "id": "D-08", + "kind": "routing", + "where": "src/web/api_handlers_streams_get.c:handle_get_stream_full", + "detail": "Stream names are path segments matched before decoding; names containing \"/\" must be percent-encoded and decoded by http_request_extract_path_param. /full handler trims everything after first \"/\" of decoded id. Contract tests should include encoded names (T11)." + }, + { + "id": "D-09", + "kind": "docs", + "where": "web/js/components/preact/Toast.jsx", + "detail": "Toast.jsx (ToastProvider/useToast) is unreferenced; duplicate toast API with ToastContainer.jsx." + }, + { + "id": "D-10", + "kind": "test-config", + "where": "playwright.config.ts", + "detail": "Project 'go2rtc' testMatch /.*\\.go2rtc\\.spec\\.ts/ matches no file; tests/integration/go2rtc.api.spec.ts runs in the 'api' project (npm run test:go2rtc relies on --grep @go2rtc instead)." + } + ], + "streams_pilot": { + "scope_note": "Pilot = Streams list + edit only (plan assumption). Create/clone/delete/disable/bulk/ONVIF/test are inventoried here as out-of-pilot operations sharing StreamsView.jsx.", + "ui_files": { + "pilot": [ + "web/js/pages/streams-page.jsx", + "web/js/components/preact/StreamsView.jsx", + "web/js/components/preact/StreamCard.jsx", + "web/js/components/preact/StreamConfigModal.jsx", + "web/js/components/preact/ZoneEditor.jsx", + "web/js/utils/stream-availability.js", + "web/js/utils/eptz-config.js" + ], + "pilot_shared_dependencies": [ + "web/js/query-client.js (useQuery/useMutation/useQueryClient/usePostMutation/fetchJSON)", + "web/js/fetch-utils.js (fetchJSON, 401 redirect)", + "web/js/i18n.js (useI18n)", + "web/js/utils/auth-utils.js (validateSession -> role, demo_mode)", + "web/js/components/preact/ToastContainer.jsx (showStatusMessage)", + "web/js/components/preact/AsyncButton.jsx", + "web/js/components/preact/LoadingIndicator.jsx (ContentLoader)", + "web/js/utils/url-utils.js (obfuscateUrlCredentials in StreamConfigModal)", + "web/js/utils/zone-editor-geometry.js", + "web/js/utils/settings-utils.js (getGo2rtcBaseUrl in ZoneEditor)" + ], + "same_file_out_of_pilot": [ + "web/js/components/preact/StreamDeleteModal.jsx", + "web/js/components/preact/StreamBulkActionModal.jsx", + "StreamsView.jsx tabs that embed HealthView.jsx, FleetView.jsx, EventRoutingView.jsx (view=health|inventory|events)", + "StreamsView.jsx ONVIF discovery modal and create/clone/test/delete/disable/enable/bulk mutations" + ] + }, + "variants": { + "summary_list": { + "operation": "GET /api/streams?summary=true", + "handler": "handle_get_streams -> handle_get_stream_summaries (src/web/api_handlers_streams_get.c)", + "auth": "401 unless authenticated (httpd_check_action_access). Rows filtered per camera: surface admin/live require AUTHZ_LIVE_VIEW; surface=lpr requires AUTHZ_LPR_SEARCH and AUTHZ_LPR_READ. can_configure = AUTHZ_CAMERA_CONFIGURE decision per row.", + "query": { + "summary": "true|1 selects this variant; otherwise legacy full list", + "surface": "admin (default) | live | lpr; anything else 400 'surface must be admin, live or lpr'", + "page": "int >=1 (default 1, max 1000000); invalid 400", + "page_size": "int 1..100 (default 50); >100 or <1 => 400 'page and page_size must be positive; page_size max is 100'", + "sort_by": "name|status|resolution|fps|recording (default name); else 400", + "sort_order": "asc|desc (default asc); else 400", + "availability": "all|live|offline|never_connected|disabled (default all); else 400", + "search": "case-insensitive substring, max 255 chars", + "include_admin_url": "true|1 adds admin_url on non-live surfaces (empty string unless can_configure)" + }, + "response": "{streams:[...], total, page, page_size, total_pages}", + "item_fields": { + "lpr": [ + "camera_uuid", + "name" + ], + "admin": [ + "camera_uuid", + "name", + "location_uuid", + "location_name", + "location_path", + "enabled", + "width", + "height", + "fps", + "codec", + "protocol", + "record", + "record_on_schedule", + "detection_based_recording", + "isOnvif", + "tags", + "status", + "availability", + "first_video_at", + "last_frame_ts", + "current_fps", + "recording_active", + "last_recording_at", + "can_configure", + "admin_url (only if include_admin_url)" + ], + "live_additional": [ + "streaming_enabled", + "playback_transport", + "eptz_config", + "detection_model", + "privacy_mode", + "can_control_privacy", + "has_sub_stream", + "ptz_enabled", + "backchannel_enabled", + "go2rtc_hls_available" + ] + }, + "redaction": "Summary never includes url, onvif credentials, sub_stream_url, detection_url, publish_url, go2rtc_source_override. admin_url is opt-in and blanked for rows without configure.", + "ui_consumers": [ + "StreamsView.jsx:199 (surface=admin, include_admin_url=true, paged, sortable, availability, search; queryKey ['streams','summary',page,size,sort,dir,search,availability])", + "utils/stream-summaries.js fetchAllStreamSummaries (pages of 100; surface admin|live|lpr) used by LiveView, PlatesView, recordings/timeline/investigation pickers" + ] + }, + "legacy_full_list": { + "operation": "GET /api/streams (no summary param)", + "handler": "handle_get_streams legacy branch", + "auth": "rows require AUTHZ_LIVE_VIEW; expose_sensitive_config = AUTHZ_CAMERA_CONFIGURE per row", + "note": "Unbounded array of full stream objects (same field set as single GET). No current web/js caller sends /api/streams without summary=true; integration tests and external API clients may. Out of pilot but must not regress." + }, + "single_get": { + "operation": "GET /api/streams/{name}", + "handler": "handle_get_stream", + "auth": "AUTHZ_LIVE_VIEW on stream (httpd_authorize_stream_action_with_context: 401/403/404); expose_sensitive_config = stream_user_can_modify (AUTHZ_CAMERA_CONFIGURE)", + "response": "bare stream object (not wrapped)", + "ui_consumers": "none for GET (PUT/DELETE share the pattern)" + }, + "full_edit_detail": { + "operation": "GET /api/streams/{name}/full", + "handler": "handle_get_stream_full", + "auth": "same as single_get: viewers with live_view receive a redacted full object; 404 'Stream not found' checked before authorization (existence disclosure ordering).", + "response": "{stream:{...}} wrapper", + "fields": [ + "camera_uuid", + "location_uuid", + "name", + "url", + "enabled", + "streaming_enabled", + "playback_transport", + "eptz_config", + "width", + "height", + "fps", + "codec", + "priority", + "record", + "segment_duration", + "detection_based_recording", + "detection_model", + "detection_threshold (int percent = float*100)", + "detection_interval", + "pre_detection_buffer", + "post_detection_buffer", + "detection_object_filter", + "detection_object_filter_list", + "protocol (int)", + "record_audio", + "audio_voice_enhancement", + "isOnvif", + "backchannel_enabled", + "retention_days", + "detection_retention_days", + "max_storage_mb", + "tier_critical_multiplier", + "tier_important_multiplier", + "tier_ephemeral_multiplier", + "storage_priority", + "ptz_enabled", + "ptz_max_x", + "ptz_max_y", + "ptz_max_z", + "ptz_has_home", + "onvif_username", + "onvif_password", + "onvif_profile", + "onvif_port", + "record_on_schedule", + "recording_schedule (168 bools)", + "detection_record_on_schedule", + "detection_recording_schedule (168 bools)", + "tags", + "admin_url", + "privacy_mode", + "can_control_privacy", + "motion_trigger_source", + "go2rtc_source_override", + "sub_stream_url", + "has_sub_stream", + "detection_url", + "publish_url", + "status", + "error_message (only when status==Error and message non-empty)", + "go2rtc_hls_available" + ], + "role_variants": { + "configure_allowed (admin edit model)": "url = raw url unless separate ONVIF credentials are in use (is_onvif or onvif_username/password set) in which case credentials are stripped from url and surfaced via onvif_username/onvif_password (extracted from url if the fields are empty); admin_url, go2rtc_source_override, sub_stream_url, detection_url, publish_url, onvif_username, onvif_password are real values; can_control_privacy=true.", + "live_view_only (viewer redacted model)": "url always credential-stripped (empty string if stripping fails); onvif_username='' and onvif_password=''; admin_url, go2rtc_source_override, sub_stream_url, detection_url, publish_url = ''; has_sub_stream still reveals presence; can_control_privacy=false. Non-sensitive config (schedules, retention, detection settings, tags) remains visible." + }, + "ui_consumers": [ + "StreamsView.jsx:808 openEditStreamModal (queryKey ['stream-full', name], staleTime 0, raw fetch not fetchJSON => no 401 redirect/timeout/retry)", + "StreamsView.jsx:900 openCloneStreamModal (out of pilot)" + ] + }, + "update": { + "operation": "PUT /api/streams/{name}", + "handler": "handle_put_stream (src/web/api_handlers_streams_modify.c)", + "auth": "404 if unknown stream, then AUTHZ_CAMERA_CONFIGURE on stream (401/403) BEFORE body parse; 400 'Invalid JSON in request body'.", + "accepted_keys": [ + "url", + "enabled", + "enable_disabled", + "streaming_enabled", + "playback_transport", + "eptz_config", + "codec", + "priority", + "protocol", + "segment_duration", + "record", + "detection_based_recording", + "detection_model", + "detection_threshold", + "detection_interval", + "pre_detection_buffer", + "post_detection_buffer", + "detection_object_filter", + "detection_object_filter_list", + "record_audio", + "audio_voice_enhancement", + "isOnvif", + "backchannel_enabled", + "onvif_username", + "onvif_password", + "onvif_profile", + "onvif_port", + "retention_days", + "detection_retention_days", + "max_storage_mb", + "tier_critical_multiplier", + "tier_important_multiplier", + "tier_ephemeral_multiplier", + "storage_priority", + "ptz_enabled", + "ptz_max_x", + "ptz_max_y", + "ptz_max_z", + "ptz_has_home", + "record_on_schedule", + "recording_schedule", + "detection_record_on_schedule", + "detection_recording_schedule", + "tags", + "admin_url", + "motion_trigger_source", + "go2rtc_source_override", + "sub_stream_url", + "detection_url", + "publish_url", + "set_privacy_mode" + ], + "not_accepted": [ + "name (rename unsupported; name is the path key and UI strips it from the body)", + "is_deleted (sent by UI, ignored)" + ], + "validation_errors": [ + "400 playback_transport is invalid", + "400 ", + "400 retention_days must be between -1 and 365", + "400 detection_retention_days must be between -1 and 365", + "400 detection_url must use http, https, rtsp, or rtsps", + "413 detection_url exceeds size limit", + "400 publish_url destinations must be rtmp:// or rtmps:// URLs", + "413 publish_url exceeds size limit" + ], + "responses": { + "202": "{success:true, message:'Stream update request accepted and processing', onvif_status?, onvif_message?} - applied asynchronously by a worker task", + "200": "set_privacy_mode short-circuit: {success:true, privacy_mode:bool} (Live cells use this; not the edit form)" + }, + "ui_payload_builder": "StreamsView.jsx handleSubmit (~line 616): maps camelCase form state to snake_case body; always sends onvif_password (possibly '' for redacted forms); detection_threshold as int percent; recording schedules normalized to 168 bools; adds is_deleted:false when editing; mutation retries:1 timeout 15000; invalidates ['stream-full', name] and ['streams'].", + "ui_consumers": [ + "StreamsView.jsx:343 saveStreamMutation (edit)", + "StreamsView.jsx:484/532 enable (out of pilot: {enable_disabled:true, enabled:true})", + "HLSVideoCell/MSEVideoCell/WebRTCVideoCell set_privacy_mode (Live, out of pilot)" + ] + } + }, + "list_edit_option_dependencies": [ + { + "operation": "GET /api/client-config", + "handler": "handle_get_client_config (api_handlers_settings.c)", + "auth": "authenticated only (401)", + "used_fields": [ + "default_playback_transport (new-stream default)", + "audio_disabled (recordAudio default + modal audioDisabled)", + "webrtc_disabled/mse_disabled/hls_disabled (transportOfferings in StreamConfigModal)" + ], + "ui": "StreamsView.jsx:227 useQuery(['client-config'])", + "full_response_fields": [ + "go2rtc_enabled", + "web_auth_enabled", + "demo_mode", + "audio_disabled", + "auto_disabled", + "go2rtc_available", + "go2rtc_api_port", + "go2rtc_base_path", + "webrtc_disabled", + "hls_disabled", + "mse_disabled", + "default_playback_transport", + "go2rtc_force_native_hls", + "webrtc_connection_timeout_ms", + "webrtc_ice_recovery_timeout_ms", + "generate_thumbnails", + "thumbnails_per_recording" + ] + }, + { + "operation": "GET /api/detection/models", + "handler": "handle_get_detection_models (api_handlers_detection_models.c)", + "auth": "401 unauthenticated; 403 unless global AUTHZ_CAMERA_CONFIGURE or configure on >=1 camera", + "response": "{models:[{id,name,path,type,supported,description,unsupported_reason?,size?...}], litert_available, sod_available, count|message}; built-ins motion, api-detection, onvif listed first", + "ui": "StreamsView.jsx:221 useQuery(['detectionModels']); StreamConfigModal filters out id 'api-detection' (rendered as fixed option) and disables supported===false; refresh button invalidates the query" + }, + { + "operation": "GET /api/auth/verify", + "handler": "handle_auth_verify", + "auth": "skipAuthRedirect; demo mode returns demo_mode:true", + "used_fields": [ + "role (admin/user/viewer gating canModifyStreams)", + "demo_mode (shouldHideCredentials)" + ], + "ui": "StreamsView.jsx:58 validateSession()" + }, + { + "operation": "GET /api/streams/{name}/zones", + "handler": "handle_get_zones (api_handlers_zones.c)", + "auth": "AUTHZ_LIVE_VIEW", + "ui": "StreamConfigModal.jsx:336 when editing (raw fetch); ZoneEditor.jsx:53" + }, + { + "operation": "POST /api/streams/{name}/zones", + "handler": "handle_post_zones", + "auth": "AUTHZ_CAMERA_CONFIGURE", + "ui": "ZoneEditor.jsx:481 save (independent of stream PUT)" + }, + { + "operation": "GET /go2rtc/api/frame.jpeg?src={name}", + "handler": "go2rtc proxy thread", + "ui": "StreamCard.jsx:140 snapshot thumbnails (concurrency-limited), ZoneEditor.jsx:82/88 background frame" + } + ], + "out_of_pilot_streams_operations": [ + "POST /api/streams (create/clone, 201)", + "POST /api/streams/test (usePostMutation)", + "DELETE /api/streams/{name} (disable/soft delete, 202 {success, permanent:false, status:'accepted'})", + "DELETE /api/streams/{name}?permanent=true (permanent delete, 202)", + "PUT /api/streams/{name} {enable_disabled:true, enabled:true} (enable/bulk enable)", + "GET /api/onvif/devices, POST /api/onvif/discovery/discover, POST /api/onvif/device/profiles, POST /api/onvif/device/test, POST /api/onvif/device/claim, GET /api/settings (onvif_discovery_network)", + "POST /api/streams/{name}/refresh, GET|POST /api/streams/{name}/recording, PTZ/imaging/daynight/retention/detection-engines sub-resources (Live/other domains)" + ], + "contract_notes_for_T11": [ + "Viewer model must allow empty-string sensitive fields and must not require onvif_password/admin_url/sub_stream_url/detection_url/publish_url/go2rtc_source_override to be non-empty; admin model is distinguished by can_control_privacy=true (per-camera configure), not by role name.", + "detection_threshold is an integer percent on the wire (server stores float 0..1).", + "recording_schedule and detection_recording_schedule are fixed-length 168-element boolean arrays (hour-of-week).", + "retention_days and detection_retention_days range -1..365 (-1 = inherit/global). Tier multipliers are floats; storage_priority 1..10 is clamped client-side only.", + "protocol is numeric (0 TCP, 1 UDP); priority numeric; UI keeps them as strings in form state.", + "PUT returns 202 (async apply) except set_privacy_mode (200). GET /full wraps in {stream}; GET /{name} is unwrapped.", + "Stream name is the path key; must be percent-encoded (UI uses encodeURIComponent). Decoding happens in http_request_extract_path_param after routing.", + "Error envelope is {\"error\": \"\"} (http_response_set_json_error, src/web/request_response.c:282) for all statuses.", + "openEditStreamModal uses raw fetch for /full (no 401 redirect handling, no timeout) while the list uses fetchJSON via useQuery." + ] + } +} diff --git a/quality/application-inventory.json b/quality/application-inventory.json new file mode 100644 index 000000000..9425c8dfb --- /dev/null +++ b/quality/application-inventory.json @@ -0,0 +1,8163 @@ +{ + "schema_version": 1, + "task": "T2", + "generated_at": "2026-10-31", + "revision": "ff6a7d60", + "generator": "scratchpad gen.py (not committed); regex import/URL extraction + hand-reviewed overrides", + "summary": { + "web_js_files": 193, + "by_extension": { + ".js": 63, + ".jsx": 130 + }, + "test_files": 109, + "html_entrypoints": 11, + "domains": { + "shared-i18n": 2, + "shared-auth": 5, + "shared-data-query": 4, + "shared-ui-primitives": 7, + "shared-shell": 14, + "streams": 11, + "live": 49, + "recordings": 16, + "timeline": 10, + "investigation": 5, + "lpr": 3, + "settings": 14, + "system-health": 20, + "users-access": 16, + "fleet": 13, + "events": 4 + }, + "orphans": 4, + "extension_sensitive_tests": 11 + }, + "domains": { + "shared-i18n": "Locale loading, t()/useI18n, language selector. Cross-cutting; migrate first (T25 shared wave).", + "shared-auth": "Session validation, login, auth gate, forced password change, 401 redirect policy.", + "shared-data-query": "fetch wrappers, @preact-signals/query adapter, request queue, async action hook.", + "shared-ui-primitives": "Toast system, modal/dialog primitives, AsyncButton, loaders, image helper.", + "shared-shell": "Header/Footer/navigation, theme, setup wizard, sentry, generic utils and settings cache.", + "streams": "Streams admin list/edit pilot (T11/T19-T22) plus create/delete/bulk/ONVIF flows and ZoneEditor.", + "live": "Unified Live view (PR #691), video transport cells, PTZ/day-night/overlays, operator navigator/plans.", + "recordings": "Recordings list/grid/table, filters, batch delete/download, playback helpers.", + "timeline": "Timeline page, ruler/segments/player/cursor.", + "investigation": "Investigation workspace, bookmarks, export actions.", + "lpr": "License-plate search page.", + "settings": "Settings page and tabs (general, storage, detection, go2rtc, auth, MQTT, health, workspaces...).", + "system-health": "System page, logs, storage/network/system health, health view/incidents.", + "users-access": "Users, roles, access policy, API tokens, TOTP, audit history.", + "fleet": "Fleet inventory, organizations/tags/locations, collections, saved views.", + "events": "Event routing destinations and routes." + }, + "entrypoints": [ + { + "vite_input": "hls", + "html": "web/hls.html", + "module_scripts": [ + "web/js/pages/hls-page.jsx" + ], + "reachable_module_count": 76 + }, + { + "vite_input": "index", + "html": "web/index.html", + "module_scripts": [ + "web/js/pages/index-page.jsx" + ], + "reachable_module_count": 77 + }, + { + "vite_input": "investigation", + "html": "web/investigation.html", + "module_scripts": [ + "web/js/pages/investigation-page.jsx" + ], + "reachable_module_count": 31 + }, + { + "vite_input": "login", + "html": "web/login.html", + "module_scripts": [ + "web/js/pages/login-page.jsx" + ], + "reachable_module_count": 10 + }, + { + "vite_input": "plates", + "html": "web/plates.html", + "module_scripts": [ + "web/js/pages/plates-page.jsx" + ], + "reachable_module_count": 23 + }, + { + "vite_input": "recordings", + "html": "web/recordings.html", + "module_scripts": [ + "web/js/pages/recordings-page.jsx" + ], + "reachable_module_count": 42 + }, + { + "vite_input": "settings", + "html": "web/settings.html", + "module_scripts": [ + "web/js/pages/settings-page.jsx" + ], + "reachable_module_count": 41 + }, + { + "vite_input": "streams", + "html": "web/streams.html", + "module_scripts": [ + "web/js/pages/streams-page.jsx" + ], + "reachable_module_count": 50 + }, + { + "vite_input": "system", + "html": "web/system.html", + "module_scripts": [ + "web/js/pages/system-page.jsx" + ], + "reachable_module_count": 38 + }, + { + "vite_input": "timeline", + "html": "web/timeline.html", + "module_scripts": [ + "web/js/pages/timeline-page.jsx" + ], + "reachable_module_count": 42 + }, + { + "vite_input": "users", + "html": "web/users.html", + "module_scripts": [ + "web/js/pages/users-page.jsx" + ], + "reachable_module_count": 37 + } + ], + "generated_untracked_modules": [ + { + "path": "web/js/version.js", + "note": "gitignored, produced by scripts/extract_version.* ; imported by Header.jsx. Not in rg --files." + } + ], + "shared_dependencies": { + "i18n": { + "modules": [ + "web/js/i18n.js" + ], + "consumers": [ + "web/js/components/preact/AlwaysFullscreenToggle.jsx", + "web/js/components/preact/AsyncButton.jsx", + "web/js/components/preact/AuthGate.jsx", + "web/js/components/preact/BatchDeleteModal.jsx", + "web/js/components/preact/BatchDownloadModal.jsx", + "web/js/components/preact/DayNightControl.jsx", + "web/js/components/preact/DetectionOverlay.jsx", + "web/js/components/preact/EventRoutingView.jsx", + "web/js/components/preact/FisheyeEptzCanvas.jsx", + "web/js/components/preact/FleetView.jsx", + "web/js/components/preact/Footer.jsx", + "web/js/components/preact/GridPicker.jsx", + "web/js/components/preact/HLSVideoCell.jsx", + "web/js/components/preact/Header.jsx", + "web/js/components/preact/HealthView.jsx", + "web/js/components/preact/LiveDisplayModeToggle.jsx", + "web/js/components/preact/LiveSlideshowControl.jsx", + "web/js/components/preact/LiveTileStatus.jsx", + "web/js/components/preact/LiveView.jsx", + "web/js/components/preact/LoadingIndicator.jsx", + "web/js/components/preact/LoginView.jsx", + "web/js/components/preact/MSEVideoCell.jsx", + "web/js/components/preact/ManualRecordingButton.jsx", + "web/js/components/preact/MobileTileGestures.jsx", + "web/js/components/preact/PTZControls.jsx", + "web/js/components/preact/PictureInPictureButton.jsx", + "web/js/components/preact/PlaybackTransportCell.jsx", + "web/js/components/preact/RecordingsView.jsx", + "web/js/components/preact/SettingsView.jsx", + "web/js/components/preact/SetupWizard.jsx", + "web/js/components/preact/SnapshotManager.jsx", + "web/js/components/preact/StreamBulkActionModal.jsx", + "web/js/components/preact/StreamConfigModal.jsx", + "web/js/components/preact/StreamDeleteModal.jsx", + "web/js/components/preact/StreamsView.jsx", + "web/js/components/preact/SystemView.jsx", + "web/js/components/preact/ThemeCustomizer.jsx", + "web/js/components/preact/TileAudioButton.jsx", + "web/js/components/preact/UI.jsx", + "web/js/components/preact/UsersView.jsx", + "web/js/components/preact/WebRTCVideoCell.jsx", + "web/js/components/preact/ZoneEditor.jsx", + "web/js/components/preact/common/LanguageSelector.jsx", + "web/js/components/preact/investigation/InvestigationView.jsx", + "web/js/components/preact/live/LiveBuildingPlan.jsx", + "web/js/components/preact/live/LiveOperatorNavigator.jsx", + "web/js/components/preact/lpr/PlatesView.jsx", + "web/js/components/preact/recordings/ActiveFilters.jsx", + "web/js/components/preact/recordings/FiltersSidebar.jsx", + "web/js/components/preact/recordings/PaginationControls.jsx", + "web/js/components/preact/recordings/RecordingsGrid.jsx", + "web/js/components/preact/recordings/RecordingsTable.jsx", + "web/js/components/preact/recordings/TagsOverlay.jsx", + "web/js/components/preact/recordings/recordingsAPI.jsx", + "web/js/components/preact/system/ClearLogsModal.jsx", + "web/js/components/preact/system/HealthIncidentHistory.jsx", + "web/js/components/preact/system/LogsView.jsx", + "web/js/components/preact/system/MemoryStorage.jsx", + "web/js/components/preact/system/NetworkInfo.jsx", + "web/js/components/preact/system/RestartModal.jsx", + "web/js/components/preact/system/StorageHealth.jsx", + "web/js/components/preact/system/StreamStorage.jsx", + "web/js/components/preact/system/StreamsInfo.jsx", + "web/js/components/preact/system/SystemControls.jsx", + "web/js/components/preact/system/SystemHealth.jsx", + "web/js/components/preact/system/SystemInfo.jsx", + "web/js/components/preact/system/VersionsTable.jsx", + "web/js/components/preact/system/WebServiceInfo.jsx", + "web/js/components/preact/timeline/CalendarPicker.jsx", + "web/js/components/preact/timeline/SpeedControls.jsx", + "web/js/components/preact/timeline/TimelineControls.jsx", + "web/js/components/preact/timeline/TimelineCursor.jsx", + "web/js/components/preact/timeline/TimelinePage.jsx", + "web/js/components/preact/timeline/TimelinePlayer.jsx", + "web/js/components/preact/timeline/TimelineSegments.jsx", + "web/js/components/preact/users/AccessPolicyModal.jsx", + "web/js/components/preact/users/AddUserModal.jsx", + "web/js/components/preact/users/ApiKeyModal.jsx", + "web/js/components/preact/users/AuditDecisionModes.jsx", + "web/js/components/preact/users/AuditHistoryModal.jsx", + "web/js/components/preact/users/DeleteUserModal.jsx", + "web/js/components/preact/users/EditUserModal.jsx", + "web/js/components/preact/users/RoleManagerModal.jsx", + "web/js/components/preact/users/TotpSetupModal.jsx", + "web/js/components/preact/users/UsersTable.jsx", + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/investigation-page.jsx", + "web/js/pages/login-page.jsx", + "web/js/pages/plates-page.jsx", + "web/js/pages/recordings-page.jsx", + "web/js/pages/settings-page.jsx", + "web/js/pages/streams-page.jsx", + "web/js/pages/system-page.jsx", + "web/js/pages/timeline-page.jsx", + "web/js/pages/users-page.jsx", + "web/js/utils/batch-delete-status.js" + ], + "consumer_count": 97 + }, + "auth": { + "modules": [ + "web/js/utils/auth-utils.js", + "web/js/components/preact/AuthGate.jsx", + "web/js/components/preact/forcedPasswordChange.js" + ], + "consumers": [ + "web/js/components/preact/FleetView.jsx", + "web/js/components/preact/Header.jsx", + "web/js/components/preact/RecordingsView.jsx", + "web/js/components/preact/SettingsView.jsx", + "web/js/components/preact/StreamsView.jsx", + "web/js/components/preact/SystemView.jsx", + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/investigation-page.jsx", + "web/js/pages/plates-page.jsx", + "web/js/pages/recordings-page.jsx", + "web/js/pages/settings-page.jsx", + "web/js/pages/streams-page.jsx", + "web/js/pages/system-page.jsx", + "web/js/pages/timeline-page.jsx", + "web/js/pages/users-page.jsx" + ], + "consumer_count": 16 + }, + "auth_failure_redirect": { + "modules": [ + "web/js/fetch-utils.js" + ], + "consumers": [ + "web/js/components/preact/lpr/PlatesView.jsx", + "web/js/components/preact/recordings/recordingsAPI.jsx", + "web/js/components/preact/system/LogsPoller.jsx", + "web/js/components/preact/users/AuditHistoryModal.jsx", + "web/js/query-client.js", + "web/js/utils/auth-utils.js", + "web/js/utils/settings-utils.js", + "web/js/utils/stream-summaries.js" + ], + "consumer_count": 8, + "note": "fetch-utils.js owns handleAuthenticationFailure()/401 redirect inside enhancedFetch/fetchJSON; every consumer of fetch-utils (direct or via query-client re-export of fetchJSON) inherits auth redirect behavior." + }, + "toast": { + "modules": [ + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/Toast.jsx" + ], + "consumers": [ + "web/js/components/preact/BatchDeleteModal.jsx", + "web/js/components/preact/BatchDownloadModal.jsx", + "web/js/components/preact/DayNightControl.jsx", + "web/js/components/preact/DetectionOverlay.jsx", + "web/js/components/preact/EventRoutingView.jsx", + "web/js/components/preact/HLSVideoCell.jsx", + "web/js/components/preact/Header.jsx", + "web/js/components/preact/LiveView.jsx", + "web/js/components/preact/MSEVideoCell.jsx", + "web/js/components/preact/ManualRecordingButton.jsx", + "web/js/components/preact/MobileTileGestures.jsx", + "web/js/components/preact/PTZControls.jsx", + "web/js/components/preact/PictureInPictureButton.jsx", + "web/js/components/preact/RecordingsView.jsx", + "web/js/components/preact/SettingsView.jsx", + "web/js/components/preact/SetupWizard.jsx", + "web/js/components/preact/SnapshotManager.jsx", + "web/js/components/preact/StreamConfigModal.jsx", + "web/js/components/preact/StreamsView.jsx", + "web/js/components/preact/SystemView.jsx", + "web/js/components/preact/UI.jsx", + "web/js/components/preact/UsersView.jsx", + "web/js/components/preact/WebRTCVideoCell.jsx", + "web/js/components/preact/events/EventDestinationEditor.jsx", + "web/js/components/preact/events/EventRouteEditor.jsx", + "web/js/components/preact/fleet/BulkOrganizeModal.jsx", + "web/js/components/preact/fleet/CollectionManager.jsx", + "web/js/components/preact/fleet/OrganizationManager.jsx", + "web/js/components/preact/fleet/SavedViewsBar.jsx", + "web/js/components/preact/live/LiveBuildingPlan.jsx", + "web/js/components/preact/live/LiveOperatorNavigator.jsx", + "web/js/components/preact/lpr/PlatesView.jsx", + "web/js/components/preact/recordings/TagsOverlay.jsx", + "web/js/components/preact/recordings/recordingsAPI.jsx", + "web/js/components/preact/settings/GeneralTab.jsx", + "web/js/components/preact/settings/StorageTab.jsx", + "web/js/components/preact/settings/WorkspacesTab.jsx", + "web/js/components/preact/timeline/SpeedControls.jsx", + "web/js/components/preact/timeline/TimelineControls.jsx", + "web/js/components/preact/timeline/TimelinePage.jsx", + "web/js/components/preact/timeline/TimelinePlayer.jsx", + "web/js/components/preact/users/AccessPolicyModal.jsx", + "web/js/components/preact/users/ApiKeyModal.jsx", + "web/js/components/preact/users/AuditDecisionModes.jsx", + "web/js/components/preact/users/AuditHistoryModal.jsx", + "web/js/components/preact/users/RoleManagerModal.jsx", + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/investigation-page.jsx", + "web/js/pages/login-page.jsx", + "web/js/pages/plates-page.jsx", + "web/js/pages/recordings-page.jsx", + "web/js/pages/settings-page.jsx", + "web/js/pages/streams-page.jsx", + "web/js/pages/system-page.jsx", + "web/js/pages/timeline-page.jsx", + "web/js/pages/users-page.jsx" + ], + "consumer_count": 57, + "note": "Two toast modules exist. ToastContainer.jsx is the live system (rendered by every page). Toast.jsx (ToastProvider/useToast) has no importers in web/js or tests: orphan candidate for T25/T34." + }, + "modal": { + "modules": [ + "web/js/components/preact/common/ModalDialog.jsx", + "web/js/components/preact/UI.jsx" + ], + "consumers": [ + "web/js/components/preact/EventRoutingView.jsx", + "web/js/components/preact/HLSVideoCell.jsx", + "web/js/components/preact/RecordingsView.jsx", + "web/js/components/preact/SnapshotManager.jsx", + "web/js/components/preact/WebRTCVideoCell.jsx", + "web/js/components/preact/ZoneEditor.jsx", + "web/js/components/preact/fleet/CollectionManager.jsx", + "web/js/components/preact/fleet/OrganizationManager.jsx", + "web/js/components/preact/fleet/SavedViewsBar.jsx", + "web/js/components/preact/live/LiveBuildingPlan.jsx", + "web/js/components/preact/live/LiveOperatorNavigator.jsx", + "web/js/components/preact/lpr/PlatesView.jsx", + "web/js/components/preact/settings/StorageTab.jsx", + "web/js/components/preact/timeline/TimelinePlayer.jsx", + "web/js/components/preact/users/AccessPolicyModal.jsx", + "web/js/components/preact/users/ApiKeyModal.jsx", + "web/js/components/preact/users/RoleManagerModal.jsx", + "web/js/pages/recordings-page.jsx" + ], + "consumer_count": 18, + "note": "UI.jsx re-exports ConfirmDialog from common/ModalDialog.jsx and owns ModalProvider/VideoModal/SnapshotPreviewModal plus imperative showVideoModal/showSnapshotPreview (dynamic import of preact)." + }, + "zone_editor": { + "modules": [ + "web/js/components/preact/ZoneEditor.jsx", + "web/js/utils/zone-editor-geometry.js" + ], + "consumers": [ + "web/js/components/preact/StreamConfigModal.jsx" + ], + "consumer_count": 1, + "note": "ZoneEditor is only consumed by StreamConfigModal (Streams edit). It loads/saves /api/streams/{name}/zones and snapshot frames via /go2rtc/api/frame.jpeg. DetectionOverlay (Live) reads the same zones route independently. zone-editor-geometry.js is shared with DetectionOverlay conventions (see zoneEditorGeometry.spec.js)." + }, + "query": { + "modules": [ + "web/js/query-client.js" + ], + "consumers": [ + "web/js/components/preact/BatchDeleteModal.jsx", + "web/js/components/preact/EventRoutingView.jsx", + "web/js/components/preact/FleetView.jsx", + "web/js/components/preact/HLSVideoCell.jsx", + "web/js/components/preact/Header.jsx", + "web/js/components/preact/HealthView.jsx", + "web/js/components/preact/LiveView.jsx", + "web/js/components/preact/MSEVideoCell.jsx", + "web/js/components/preact/RecordingsView.jsx", + "web/js/components/preact/SettingsView.jsx", + "web/js/components/preact/StreamsView.jsx", + "web/js/components/preact/SystemView.jsx", + "web/js/components/preact/UsersView.jsx", + "web/js/components/preact/WebRTCVideoCell.jsx", + "web/js/components/preact/fleet/BulkOrganizeModal.jsx", + "web/js/components/preact/fleet/CollectionManager.jsx", + "web/js/components/preact/fleet/CollectionSelectorBuilder.jsx", + "web/js/components/preact/fleet/OrganizationManager.jsx", + "web/js/components/preact/fleet/SavedViewsBar.jsx", + "web/js/components/preact/fleet/StaticCollectionMembers.jsx", + "web/js/components/preact/fleet/collectionMembership.js", + "web/js/components/preact/investigation/InvestigationActions.jsx", + "web/js/components/preact/investigation/InvestigationBookmarks.jsx", + "web/js/components/preact/investigation/InvestigationView.jsx", + "web/js/components/preact/live/LiveBuildingPlan.jsx", + "web/js/components/preact/live/LiveOperatorNavigator.jsx", + "web/js/components/preact/live/cameraFavorites.js", + "web/js/components/preact/lpr/PlatesView.jsx", + "web/js/components/preact/recordings/recordingsAPI.jsx", + "web/js/components/preact/settings/Go2rtcTab.jsx", + "web/js/components/preact/settings/StorageTab.jsx", + "web/js/components/preact/settings/WorkspacesTab.jsx", + "web/js/components/preact/system/HealthIncidentHistory.jsx", + "web/js/components/preact/system/StorageHealth.jsx", + "web/js/components/preact/system/SystemHealth.jsx", + "web/js/components/preact/system/WebServiceInfo.jsx", + "web/js/components/preact/timeline/TimelinePage.jsx", + "web/js/components/preact/users/AccessPolicyModal.jsx", + "web/js/components/preact/users/ApiKeyModal.jsx", + "web/js/components/preact/users/AuditDecisionModes.jsx", + "web/js/components/preact/users/AuditHistoryModal.jsx", + "web/js/components/preact/users/RoleManagerModal.jsx", + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/investigation-page.jsx", + "web/js/pages/login-page.jsx", + "web/js/pages/plates-page.jsx", + "web/js/pages/recordings-page.jsx", + "web/js/pages/settings-page.jsx", + "web/js/pages/streams-page.jsx", + "web/js/pages/system-page.jsx", + "web/js/pages/timeline-page.jsx", + "web/js/pages/users-page.jsx" + ], + "consumer_count": 53, + "note": "query-client.js wraps @preact-signals/query: useQuery(key,url,fetchOpts,queryOpts), useMutation, usePost/Put/DeleteMutation(url), QueryClientProvider/queryClient singleton, and re-exports fetchJSON. Every page entry wraps its tree in QueryClientProvider." + } + }, + "files": [ + { + "path": "web/js/components/preact/AlwaysFullscreenToggle.jsx", + "ext": ".jsx", + "lines": 25, + "kind": "component", + "proposed_domain_owner": "live", + "imports": [ + "web/js/i18n.js" + ], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/LiveView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [ + "i18n" + ], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/AsyncButton.jsx", + "ext": ".jsx", + "lines": 297, + "kind": "component", + "proposed_domain_owner": "shared-ui-primitives", + "imports": [ + "web/js/hooks/useAsyncAction.js", + "web/js/i18n.js" + ], + "external_imports": [ + "preact", + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/RecordingsView.jsx", + "web/js/components/preact/SettingsView.jsx", + "web/js/components/preact/StreamBulkActionModal.jsx", + "web/js/components/preact/StreamCard.jsx", + "web/js/components/preact/StreamConfigModal.jsx", + "web/js/components/preact/StreamDeleteModal.jsx", + "web/js/components/preact/recordings/FiltersSidebar.jsx", + "web/js/components/preact/settings/AuthTab.jsx", + "web/js/components/preact/system/RestartModal.jsx", + "web/js/components/preact/users/AddUserModal.jsx", + "web/js/components/preact/users/DeleteUserModal.jsx", + "web/js/components/preact/users/EditUserModal.jsx", + "web/js/components/preact/users/TotpSetupModal.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/investigation-page.jsx", + "web/js/pages/plates-page.jsx", + "web/js/pages/recordings-page.jsx", + "web/js/pages/settings-page.jsx", + "web/js/pages/streams-page.jsx", + "web/js/pages/system-page.jsx", + "web/js/pages/timeline-page.jsx", + "web/js/pages/users-page.jsx" + ], + "shared_dependencies_used": [ + "i18n" + ], + "ui_operation_ids": [], + "tests": [], + "consumed_by_other_domains": [ + "recordings", + "settings", + "streams", + "system-health", + "users-access" + ] + }, + { + "path": "web/js/components/preact/AuthGate.jsx", + "ext": ".jsx", + "lines": 171, + "kind": "component", + "proposed_domain_owner": "shared-auth", + "imports": [ + "web/js/components/preact/forcedPasswordChange.js", + "web/js/i18n.js", + "web/js/utils/auth-utils.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/investigation-page.jsx", + "web/js/pages/plates-page.jsx", + "web/js/pages/recordings-page.jsx", + "web/js/pages/settings-page.jsx", + "web/js/pages/streams-page.jsx", + "web/js/pages/system-page.jsx", + "web/js/pages/timeline-page.jsx", + "web/js/pages/users-page.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/investigation-page.jsx", + "web/js/pages/plates-page.jsx", + "web/js/pages/recordings-page.jsx", + "web/js/pages/settings-page.jsx", + "web/js/pages/streams-page.jsx", + "web/js/pages/system-page.jsx", + "web/js/pages/timeline-page.jsx", + "web/js/pages/users-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "auth" + ], + "ui_operation_ids": [ + "UI-001" + ], + "tests": [], + "consumed_by_other_domains": [ + "investigation", + "live", + "lpr", + "recordings", + "settings", + "streams", + "system-health", + "timeline", + "users-access" + ] + }, + { + "path": "web/js/components/preact/BatchDeleteModal.jsx", + "ext": ".jsx", + "lines": 505, + "kind": "component", + "proposed_domain_owner": "recordings", + "imports": [ + "web/js/components/preact/ToastContainer.jsx", + "web/js/i18n.js", + "web/js/query-client.js", + "web/js/utils/batch-delete-status.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/pages/recordings-page.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/recordings-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "toast", + "query" + ], + "ui_operation_ids": [ + "UI-002", + "UI-003" + ], + "tests": [] + }, + { + "path": "web/js/components/preact/BatchDownloadModal.jsx", + "ext": ".jsx", + "lines": 215, + "kind": "component", + "proposed_domain_owner": "recordings", + "imports": [ + "web/js/components/preact/ToastContainer.jsx", + "web/js/i18n.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/RecordingsView.jsx", + "web/js/components/preact/timeline/TimelinePage.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/recordings-page.jsx", + "web/js/pages/timeline-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "toast" + ], + "ui_operation_ids": [ + "UI-004", + "UI-005", + "UI-006", + "UI-007" + ], + "tests": [], + "i18n_literal_baseline_key": "preact/BatchDownloadModal.jsx", + "i18n_literal_baseline_count": 1, + "consumed_by_other_domains": [ + "timeline" + ] + }, + { + "path": "web/js/components/preact/DayNightControl.jsx", + "ext": ".jsx", + "lines": 181, + "kind": "component", + "proposed_domain_owner": "live", + "imports": [ + "web/js/components/preact/ToastContainer.jsx", + "web/js/i18n.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/HLSVideoCell.jsx", + "web/js/components/preact/MSEVideoCell.jsx", + "web/js/components/preact/WebRTCVideoCell.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "toast" + ], + "ui_operation_ids": [ + "UI-008", + "UI-009" + ], + "tests": [] + }, + { + "path": "web/js/components/preact/DetectionOverlay.jsx", + "ext": ".jsx", + "lines": 318, + "kind": "component", + "proposed_domain_owner": "live", + "imports": [ + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/useLiveDisplayMode.js", + "web/js/i18n.js", + "web/js/utils/date-utils.js", + "web/js/utils/detection-polling.js" + ], + "external_imports": [ + "preact/compat", + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/HLSVideoCell.jsx", + "web/js/components/preact/MSEVideoCell.jsx", + "web/js/components/preact/WebRTCVideoCell.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "toast" + ], + "ui_operation_ids": [ + "UI-010" + ], + "tests": [] + }, + { + "path": "web/js/components/preact/EventRoutingView.jsx", + "ext": ".jsx", + "lines": 226, + "kind": "component", + "proposed_domain_owner": "events", + "imports": [ + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/common/ModalDialog.jsx", + "web/js/components/preact/events/EventDestinationEditor.jsx", + "web/js/components/preact/events/EventRouteEditor.jsx", + "web/js/components/preact/events/eventRouting.js", + "web/js/i18n.js", + "web/js/query-client.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/StreamsView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/streams-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "toast", + "modal", + "query" + ], + "ui_operation_ids": [ + "UI-011", + "UI-012", + "UI-013", + "UI-014", + "UI-015", + "UI-016", + "UI-017", + "UI-018", + "UI-019", + "UI-020", + "UI-021", + "UI-022", + "UI-023", + "UI-024", + "UI-025" + ], + "tests": [], + "i18n_literal_baseline_key": "preact/EventRoutingView.jsx", + "i18n_literal_baseline_count": 1, + "consumed_by_other_domains": [ + "streams" + ] + }, + { + "path": "web/js/components/preact/FisheyeEptzCanvas.jsx", + "ext": ".jsx", + "lines": 506, + "kind": "component", + "proposed_domain_owner": "live", + "imports": [ + "web/js/i18n.js", + "web/js/utils/eptz-config.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/HLSVideoCell.jsx", + "web/js/components/preact/MSEVideoCell.jsx", + "web/js/components/preact/WebRTCVideoCell.jsx", + "web/js/components/preact/investigation/InvestigationView.jsx", + "web/js/components/preact/timeline/TimelinePlayer.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/investigation-page.jsx", + "web/js/pages/timeline-page.jsx" + ], + "shared_dependencies_used": [ + "i18n" + ], + "ui_operation_ids": [], + "tests": [], + "i18n_literal_baseline_key": "preact/FisheyeEptzCanvas.jsx", + "i18n_literal_baseline_count": 1, + "consumed_by_other_domains": [ + "investigation", + "timeline" + ] + }, + { + "path": "web/js/components/preact/FleetView.jsx", + "ext": ".jsx", + "lines": 421, + "kind": "component", + "proposed_domain_owner": "fleet", + "imports": [ + "web/js/components/preact/LoadingIndicator.jsx", + "web/js/components/preact/fleet/BulkOrganizeModal.jsx", + "web/js/components/preact/fleet/CollectionManager.jsx", + "web/js/components/preact/fleet/FleetFilters.jsx", + "web/js/components/preact/fleet/FleetTable.jsx", + "web/js/components/preact/fleet/OrganizationManager.jsx", + "web/js/components/preact/fleet/SavedViewsBar.jsx", + "web/js/components/preact/fleet/fleetOrganization.js", + "web/js/components/preact/fleet/fleetQuery.js", + "web/js/i18n.js", + "web/js/query-client.js", + "web/js/utils/auth-utils.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/StreamsView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/streams-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "auth", + "query" + ], + "ui_operation_ids": [ + "UI-026", + "UI-027", + "UI-028", + "UI-029", + "UI-030" + ], + "tests": [], + "consumed_by_other_domains": [ + "streams" + ] + }, + { + "path": "web/js/components/preact/Footer.jsx", + "ext": ".jsx", + "lines": 36, + "kind": "component", + "proposed_domain_owner": "shared-shell", + "imports": [ + "web/js/i18n.js", + "web/js/utils/date-utils.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/investigation-page.jsx", + "web/js/pages/plates-page.jsx", + "web/js/pages/recordings-page.jsx", + "web/js/pages/settings-page.jsx", + "web/js/pages/streams-page.jsx", + "web/js/pages/system-page.jsx", + "web/js/pages/timeline-page.jsx", + "web/js/pages/users-page.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/investigation-page.jsx", + "web/js/pages/plates-page.jsx", + "web/js/pages/recordings-page.jsx", + "web/js/pages/settings-page.jsx", + "web/js/pages/streams-page.jsx", + "web/js/pages/system-page.jsx", + "web/js/pages/timeline-page.jsx", + "web/js/pages/users-page.jsx" + ], + "shared_dependencies_used": [ + "i18n" + ], + "ui_operation_ids": [], + "tests": [ + "web/tests/i18n.spec.js" + ], + "i18n_literal_baseline_key": "preact/Footer.jsx", + "i18n_literal_baseline_count": 1, + "consumed_by_other_domains": [ + "investigation", + "live", + "lpr", + "recordings", + "settings", + "streams", + "system-health", + "timeline", + "users-access" + ] + }, + { + "path": "web/js/components/preact/FullscreenManager.jsx", + "ext": ".jsx", + "lines": 526, + "kind": "component", + "proposed_domain_owner": "live", + "imports": [ + "web/js/components/preact/fullscreenApi.js", + "web/js/components/preact/mobileLiveGestures.js" + ], + "external_imports": [ + "preact/hooks", + "tinykeys" + ], + "imported_by": [ + "web/js/components/preact/LiveView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/GridPicker.jsx", + "ext": ".jsx", + "lines": 147, + "kind": "component", + "proposed_domain_owner": "live", + "imports": [ + "web/js/i18n.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/LiveView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [ + "i18n" + ], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/HLSVideoCell.jsx", + "ext": ".jsx", + "lines": 1376, + "kind": "component", + "proposed_domain_owner": "live", + "imports": [ + "web/js/components/preact/DayNightControl.jsx", + "web/js/components/preact/DetectionOverlay.jsx", + "web/js/components/preact/FisheyeEptzCanvas.jsx", + "web/js/components/preact/LiveTileStatus.jsx", + "web/js/components/preact/LoadingIndicator.jsx", + "web/js/components/preact/ManualRecordingButton.jsx", + "web/js/components/preact/MobileTileGestures.jsx", + "web/js/components/preact/PTZControls.jsx", + "web/js/components/preact/PictureInPictureButton.jsx", + "web/js/components/preact/SnapshotManager.jsx", + "web/js/components/preact/TileAudioButton.jsx", + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/UI.jsx", + "web/js/components/preact/recordings/formatUtils.js", + "web/js/components/preact/useAlwaysFullscreenOnTap.js", + "web/js/components/preact/useAutoRetry.js", + "web/js/components/preact/useVideoZoom.js", + "web/js/i18n.js", + "web/js/query-client.js", + "web/js/utils/date-utils.js", + "web/js/utils/eptz-config.js", + "web/js/utils/navigation-utils.js", + "web/js/utils/player-telemetry.js", + "web/js/utils/settings-utils.js", + "web/js/utils/stream-connection-gate.js" + ], + "external_imports": [ + "hls.js", + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/PlaybackTransportCell.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "toast", + "modal", + "query" + ], + "ui_operation_ids": [ + "UI-031", + "UI-032", + "UI-033", + "UI-034", + "UI-035", + "UI-036", + "UI-037", + "UI-296", + "UI-297" + ], + "tests": [], + "i18n_literal_baseline_key": "preact/HLSVideoCell.jsx", + "i18n_literal_baseline_count": 3 + }, + { + "path": "web/js/components/preact/Header.jsx", + "ext": ".jsx", + "lines": 420, + "kind": "component", + "proposed_domain_owner": "shared-shell", + "imports": [ + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/common/LanguageSelector.jsx", + "web/js/components/preact/users/EditUserModal.jsx", + "web/js/i18n.js", + "web/js/query-client.js", + "web/js/utils/auth-utils.js", + "web/js/utils/navigation-utils.js", + "web/js/utils/settings-utils.js", + "web/js/version.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/investigation-page.jsx", + "web/js/pages/plates-page.jsx", + "web/js/pages/recordings-page.jsx", + "web/js/pages/settings-page.jsx", + "web/js/pages/streams-page.jsx", + "web/js/pages/system-page.jsx", + "web/js/pages/timeline-page.jsx", + "web/js/pages/users-page.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/investigation-page.jsx", + "web/js/pages/plates-page.jsx", + "web/js/pages/recordings-page.jsx", + "web/js/pages/settings-page.jsx", + "web/js/pages/streams-page.jsx", + "web/js/pages/system-page.jsx", + "web/js/pages/timeline-page.jsx", + "web/js/pages/users-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "auth", + "toast", + "query" + ], + "ui_operation_ids": [ + "UI-038", + "UI-039" + ], + "tests": [], + "i18n_literal_baseline_key": "preact/Header.jsx", + "i18n_literal_baseline_count": 2, + "consumed_by_other_domains": [ + "investigation", + "live", + "lpr", + "recordings", + "settings", + "streams", + "system-health", + "timeline", + "users-access" + ] + }, + { + "path": "web/js/components/preact/HealthView.jsx", + "ext": ".jsx", + "lines": 253, + "kind": "component", + "proposed_domain_owner": "system-health", + "imports": [ + "web/js/components/preact/health/Sparkline.jsx", + "web/js/i18n.js", + "web/js/query-client.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/StreamsView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/streams-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "query" + ], + "ui_operation_ids": [ + "UI-040", + "UI-041" + ], + "tests": [], + "i18n_literal_baseline_key": "preact/HealthView.jsx", + "i18n_literal_baseline_count": 1, + "consumed_by_other_domains": [ + "streams" + ] + }, + { + "path": "web/js/components/preact/LiveDisplayModeToggle.jsx", + "ext": ".jsx", + "lines": 71, + "kind": "component", + "proposed_domain_owner": "live", + "imports": [ + "web/js/components/preact/useLiveDisplayMode.js", + "web/js/i18n.js" + ], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/LiveView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [ + "i18n" + ], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/LiveSlideshowControl.jsx", + "ext": ".jsx", + "lines": 48, + "kind": "component", + "proposed_domain_owner": "live", + "imports": [ + "web/js/components/preact/useLiveSlideshow.js", + "web/js/i18n.js" + ], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/LiveView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [ + "i18n" + ], + "ui_operation_ids": [], + "tests": [ + "web/tests/liveSlideshowControl.spec.js" + ] + }, + { + "path": "web/js/components/preact/LiveTileStatus.jsx", + "ext": ".jsx", + "lines": 99, + "kind": "component", + "proposed_domain_owner": "live", + "imports": [ + "web/js/components/preact/liveTileStatus.js", + "web/js/i18n.js" + ], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/HLSVideoCell.jsx", + "web/js/components/preact/MSEVideoCell.jsx", + "web/js/components/preact/WebRTCVideoCell.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [ + "i18n" + ], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/LiveView.jsx", + "ext": ".jsx", + "lines": 1096, + "kind": "component", + "proposed_domain_owner": "live", + "imports": [ + "web/js/components/preact/AlwaysFullscreenToggle.jsx", + "web/js/components/preact/FullscreenManager.jsx", + "web/js/components/preact/GridPicker.jsx", + "web/js/components/preact/LiveDisplayModeToggle.jsx", + "web/js/components/preact/LiveSlideshowControl.jsx", + "web/js/components/preact/PlaybackTransportCell.jsx", + "web/js/components/preact/SnapshotManager.jsx", + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/fleet/collectionMembership.js", + "web/js/components/preact/live/LiveBuildingPlan.jsx", + "web/js/components/preact/live/LiveOperatorNavigator.jsx", + "web/js/components/preact/live/cameraFavorites.js", + "web/js/components/preact/live/liveOperator.js", + "web/js/components/preact/mobileLiveGestures.js", + "web/js/components/preact/useAlwaysFullscreenOnTap.js", + "web/js/components/preact/useCameraOrder.js", + "web/js/components/preact/useLiveDisplayMode.js", + "web/js/components/preact/useLiveSlideshow.js", + "web/js/components/preact/usePullToRefresh.js", + "web/js/i18n.js", + "web/js/query-client.js", + "web/js/utils/live-view-url.js", + "web/js/utils/settings-utils.js", + "web/js/utils/stream-summaries.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "toast", + "query" + ], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/LoadingIndicator.jsx", + "ext": ".jsx", + "lines": 87, + "kind": "component", + "proposed_domain_owner": "shared-ui-primitives", + "imports": [ + "web/js/i18n.js" + ], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/FleetView.jsx", + "web/js/components/preact/HLSVideoCell.jsx", + "web/js/components/preact/MSEVideoCell.jsx", + "web/js/components/preact/RecordingsView.jsx", + "web/js/components/preact/SettingsView.jsx", + "web/js/components/preact/StreamsView.jsx", + "web/js/components/preact/SystemView.jsx", + "web/js/components/preact/WebRTCVideoCell.jsx", + "web/js/components/preact/investigation/InvestigationView.jsx", + "web/js/components/preact/timeline/TimelinePage.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/investigation-page.jsx", + "web/js/pages/recordings-page.jsx", + "web/js/pages/settings-page.jsx", + "web/js/pages/streams-page.jsx", + "web/js/pages/system-page.jsx", + "web/js/pages/timeline-page.jsx" + ], + "shared_dependencies_used": [ + "i18n" + ], + "ui_operation_ids": [], + "tests": [], + "consumed_by_other_domains": [ + "fleet", + "investigation", + "live", + "recordings", + "settings", + "streams", + "system-health", + "timeline" + ] + }, + { + "path": "web/js/components/preact/LoginView.jsx", + "ext": ".jsx", + "lines": 455, + "kind": "component", + "proposed_domain_owner": "shared-auth", + "imports": [ + "web/js/components/preact/common/LanguageSelector.jsx", + "web/js/i18n.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/pages/login-page.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/login-page.jsx" + ], + "shared_dependencies_used": [ + "i18n" + ], + "ui_operation_ids": [ + "UI-042", + "UI-043", + "UI-044", + "UI-045" + ], + "tests": [], + "i18n_literal_baseline_key": "preact/LoginView.jsx", + "i18n_literal_baseline_count": 1 + }, + { + "path": "web/js/components/preact/MSEVideoCell.jsx", + "ext": ".jsx", + "lines": 1232, + "kind": "component", + "proposed_domain_owner": "live", + "imports": [ + "web/js/components/preact/DayNightControl.jsx", + "web/js/components/preact/DetectionOverlay.jsx", + "web/js/components/preact/FisheyeEptzCanvas.jsx", + "web/js/components/preact/LiveTileStatus.jsx", + "web/js/components/preact/LoadingIndicator.jsx", + "web/js/components/preact/ManualRecordingButton.jsx", + "web/js/components/preact/MobileTileGestures.jsx", + "web/js/components/preact/PTZControls.jsx", + "web/js/components/preact/PictureInPictureButton.jsx", + "web/js/components/preact/SnapshotManager.jsx", + "web/js/components/preact/TileAudioButton.jsx", + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/recordings/formatUtils.js", + "web/js/components/preact/useAlwaysFullscreenOnTap.js", + "web/js/components/preact/useAutoRetry.js", + "web/js/components/preact/useVideoZoom.js", + "web/js/i18n.js", + "web/js/query-client.js", + "web/js/utils/date-utils.js", + "web/js/utils/eptz-config.js", + "web/js/utils/live-video-errors.js", + "web/js/utils/navigation-utils.js", + "web/js/utils/player-telemetry.js", + "web/js/utils/settings-utils.js", + "web/js/utils/stream-connection-gate.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/PlaybackTransportCell.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "toast", + "query" + ], + "ui_operation_ids": [ + "UI-046", + "UI-047", + "UI-048" + ], + "tests": [], + "i18n_literal_baseline_key": "preact/MSEVideoCell.jsx", + "i18n_literal_baseline_count": 2 + }, + { + "path": "web/js/components/preact/ManualRecordingButton.jsx", + "ext": ".jsx", + "lines": 100, + "kind": "component", + "proposed_domain_owner": "live", + "imports": [ + "web/js/components/preact/ToastContainer.jsx", + "web/js/i18n.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/HLSVideoCell.jsx", + "web/js/components/preact/MSEVideoCell.jsx", + "web/js/components/preact/WebRTCVideoCell.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "toast" + ], + "ui_operation_ids": [ + "UI-049" + ], + "tests": [] + }, + { + "path": "web/js/components/preact/MobileTileGestures.jsx", + "ext": ".jsx", + "lines": 262, + "kind": "component", + "proposed_domain_owner": "live", + "imports": [ + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/mobileLiveGestures.js", + "web/js/i18n.js", + "web/js/utils/date-utils.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/HLSVideoCell.jsx", + "web/js/components/preact/MSEVideoCell.jsx", + "web/js/components/preact/WebRTCVideoCell.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "toast" + ], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/PTZControls.jsx", + "ext": ".jsx", + "lines": 426, + "kind": "component", + "proposed_domain_owner": "live", + "imports": [ + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/ptzActions.js", + "web/js/i18n.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/HLSVideoCell.jsx", + "web/js/components/preact/MSEVideoCell.jsx", + "web/js/components/preact/WebRTCVideoCell.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "toast" + ], + "ui_operation_ids": [ + "UI-050", + "UI-051", + "UI-052", + "UI-053" + ], + "tests": [] + }, + { + "path": "web/js/components/preact/PictureInPictureButton.jsx", + "ext": ".jsx", + "lines": 62, + "kind": "component", + "proposed_domain_owner": "live", + "imports": [ + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/pictureInPicture.js", + "web/js/i18n.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/HLSVideoCell.jsx", + "web/js/components/preact/MSEVideoCell.jsx", + "web/js/components/preact/WebRTCVideoCell.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "toast" + ], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/PlaybackTransportCell.jsx", + "ext": ".jsx", + "lines": 103, + "kind": "component", + "proposed_domain_owner": "live", + "imports": [ + "web/js/components/preact/HLSVideoCell.jsx", + "web/js/components/preact/MSEVideoCell.jsx", + "web/js/components/preact/WebRTCVideoCell.jsx", + "web/js/i18n.js", + "web/js/utils/playback-transport.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/LiveView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [ + "i18n" + ], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/RecordingsView.jsx", + "ext": ".jsx", + "lines": 1216, + "kind": "component", + "proposed_domain_owner": "recordings", + "imports": [ + "web/js/components/preact/AsyncButton.jsx", + "web/js/components/preact/BatchDownloadModal.jsx", + "web/js/components/preact/LoadingIndicator.jsx", + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/UI.jsx", + "web/js/components/preact/recordings/ActiveFilters.jsx", + "web/js/components/preact/recordings/FiltersSidebar.jsx", + "web/js/components/preact/recordings/PaginationControls.jsx", + "web/js/components/preact/recordings/RecordingsGrid.jsx", + "web/js/components/preact/recordings/RecordingsTable.jsx", + "web/js/components/preact/recordings/formatUtils.js", + "web/js/components/preact/recordings/recordingsAPI.jsx", + "web/js/components/preact/recordings/urlUtils.js", + "web/js/components/preact/usePullToRefresh.js", + "web/js/i18n.js", + "web/js/query-client.js", + "web/js/request-queue.js", + "web/js/utils/auth-utils.js", + "web/js/utils/date-utils.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/pages/recordings-page.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/recordings-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "auth", + "toast", + "modal", + "query" + ], + "ui_operation_ids": [ + "UI-054", + "UI-055", + "UI-056", + "UI-057", + "UI-058" + ], + "tests": [] + }, + { + "path": "web/js/components/preact/SettingsView.jsx", + "ext": ".jsx", + "lines": 944, + "kind": "component", + "proposed_domain_owner": "settings", + "imports": [ + "web/js/components/preact/AsyncButton.jsx", + "web/js/components/preact/LoadingIndicator.jsx", + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/settings/AdvancedTab.jsx", + "web/js/components/preact/settings/AppearanceTab.jsx", + "web/js/components/preact/settings/AuthTab.jsx", + "web/js/components/preact/settings/DetectionTab.jsx", + "web/js/components/preact/settings/GeneralTab.jsx", + "web/js/components/preact/settings/Go2rtcTab.jsx", + "web/js/components/preact/settings/HealthTab.jsx", + "web/js/components/preact/settings/MqttTab.jsx", + "web/js/components/preact/settings/StorageTab.jsx", + "web/js/components/preact/settings/StreamsDefaultsTab.jsx", + "web/js/components/preact/settings/WorkspacesTab.jsx", + "web/js/i18n.js", + "web/js/query-client.js", + "web/js/utils/auth-utils.js", + "web/js/utils/date-utils.js", + "web/js/utils/reduceMotion.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/pages/settings-page.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/settings-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "auth", + "toast", + "query" + ], + "ui_operation_ids": [ + "UI-059", + "UI-060", + "UI-061", + "UI-062", + "UI-063", + "UI-064" + ], + "tests": [] + }, + { + "path": "web/js/components/preact/SetupWizard.jsx", + "ext": ".jsx", + "lines": 340, + "kind": "component", + "proposed_domain_owner": "shared-shell", + "imports": [ + "web/js/components/preact/ToastContainer.jsx", + "web/js/i18n.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/pages/index-page.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "toast" + ], + "ui_operation_ids": [ + "UI-065", + "UI-066", + "UI-067", + "UI-068", + "UI-069" + ], + "tests": [], + "i18n_literal_baseline_key": "preact/SetupWizard.jsx", + "i18n_literal_baseline_count": 2, + "consumed_by_other_domains": [ + "live" + ] + }, + { + "path": "web/js/components/preact/SnapshotManager.jsx", + "ext": ".jsx", + "lines": 267, + "kind": "component", + "proposed_domain_owner": "live", + "imports": [ + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/UI.jsx", + "web/js/i18n.js", + "web/js/utils/date-utils.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/HLSVideoCell.jsx", + "web/js/components/preact/LiveView.jsx", + "web/js/components/preact/MSEVideoCell.jsx", + "web/js/components/preact/WebRTCVideoCell.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "toast", + "modal" + ], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/StreamBulkActionModal.jsx", + "ext": ".jsx", + "lines": 132, + "kind": "component", + "proposed_domain_owner": "streams", + "imports": [ + "web/js/components/preact/AsyncButton.jsx", + "web/js/i18n.js" + ], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/StreamsView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/streams-page.jsx" + ], + "shared_dependencies_used": [ + "i18n" + ], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/StreamCard.jsx", + "ext": ".jsx", + "lines": 540, + "kind": "component", + "proposed_domain_owner": "streams", + "imports": [ + "web/js/components/preact/AsyncButton.jsx" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/StreamsView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/streams-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [ + "UI-070" + ], + "tests": [], + "i18n_literal_baseline_key": "preact/StreamCard.jsx", + "i18n_literal_baseline_count": 2 + }, + { + "path": "web/js/components/preact/StreamConfigModal.jsx", + "ext": ".jsx", + "lines": 1736, + "kind": "component", + "proposed_domain_owner": "streams", + "imports": [ + "web/js/components/preact/AsyncButton.jsx", + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/ZoneEditor.jsx", + "web/js/i18n.js", + "web/js/utils/url-utils.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/StreamsView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/streams-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "toast", + "zone_editor" + ], + "ui_operation_ids": [ + "UI-071" + ], + "tests": [], + "i18n_literal_baseline_key": "preact/StreamConfigModal.jsx", + "i18n_literal_baseline_count": 12 + }, + { + "path": "web/js/components/preact/StreamDeleteModal.jsx", + "ext": ".jsx", + "lines": 159, + "kind": "component", + "proposed_domain_owner": "streams", + "imports": [ + "web/js/components/preact/AsyncButton.jsx", + "web/js/i18n.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/StreamsView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/streams-page.jsx" + ], + "shared_dependencies_used": [ + "i18n" + ], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/StreamsView.jsx", + "ext": ".jsx", + "lines": 2070, + "kind": "component", + "proposed_domain_owner": "streams", + "imports": [ + "web/js/components/preact/EventRoutingView.jsx", + "web/js/components/preact/FleetView.jsx", + "web/js/components/preact/HealthView.jsx", + "web/js/components/preact/LoadingIndicator.jsx", + "web/js/components/preact/StreamBulkActionModal.jsx", + "web/js/components/preact/StreamCard.jsx", + "web/js/components/preact/StreamConfigModal.jsx", + "web/js/components/preact/StreamDeleteModal.jsx", + "web/js/components/preact/ToastContainer.jsx", + "web/js/i18n.js", + "web/js/query-client.js", + "web/js/utils/auth-utils.js", + "web/js/utils/eptz-config.js", + "web/js/utils/stream-availability.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/pages/streams-page.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/streams-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "auth", + "toast", + "query" + ], + "ui_operation_ids": [ + "UI-072", + "UI-073", + "UI-074", + "UI-075", + "UI-076", + "UI-077", + "UI-078", + "UI-079", + "UI-080", + "UI-081", + "UI-082", + "UI-083", + "UI-084", + "UI-085", + "UI-086", + "UI-087", + "UI-088", + "UI-089", + "UI-090", + "UI-091" + ], + "tests": [ + "web/tests/streamAvailability.spec.js" + ], + "i18n_literal_baseline_key": "preact/StreamsView.jsx", + "i18n_literal_baseline_count": 3 + }, + { + "path": "web/js/components/preact/SystemView.jsx", + "ext": ".jsx", + "lines": 392, + "kind": "component", + "proposed_domain_owner": "system-health", + "imports": [ + "web/js/components/preact/LoadingIndicator.jsx", + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/system/ClearLogsModal.jsx", + "web/js/components/preact/system/LogsPoller.jsx", + "web/js/components/preact/system/LogsView.jsx", + "web/js/components/preact/system/MemoryStorage.jsx", + "web/js/components/preact/system/NetworkInfo.jsx", + "web/js/components/preact/system/StorageHealth.jsx", + "web/js/components/preact/system/StreamStorage.jsx", + "web/js/components/preact/system/StreamsInfo.jsx", + "web/js/components/preact/system/SystemControls.jsx", + "web/js/components/preact/system/SystemHealth.jsx", + "web/js/components/preact/system/SystemInfo.jsx", + "web/js/components/preact/system/SystemUtils.js", + "web/js/components/preact/system/VersionsTable.jsx", + "web/js/components/preact/system/WebServiceInfo.jsx", + "web/js/i18n.js", + "web/js/query-client.js", + "web/js/utils/auth-utils.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/pages/system-page.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/system-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "auth", + "toast", + "query" + ], + "ui_operation_ids": [ + "UI-092", + "UI-093", + "UI-094" + ], + "tests": [] + }, + { + "path": "web/js/components/preact/ThemeCustomizer.jsx", + "ext": ".jsx", + "lines": 260, + "kind": "component", + "proposed_domain_owner": "shared-shell", + "imports": [ + "web/js/i18n.js", + "web/js/utils/theme-init.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/settings/AppearanceTab.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/settings-page.jsx" + ], + "shared_dependencies_used": [ + "i18n" + ], + "ui_operation_ids": [], + "tests": [], + "consumed_by_other_domains": [ + "settings" + ] + }, + { + "path": "web/js/components/preact/TileAudioButton.jsx", + "ext": ".jsx", + "lines": 45, + "kind": "component", + "proposed_domain_owner": "live", + "imports": [ + "web/js/i18n.js" + ], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/HLSVideoCell.jsx", + "web/js/components/preact/MSEVideoCell.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [ + "i18n" + ], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/Toast.jsx", + "ext": ".jsx", + "lines": 392, + "kind": "component", + "proposed_domain_owner": "shared-ui-primitives", + "imports": [ + "web/js/utils/date-utils.js" + ], + "external_imports": [ + "preact", + "preact/hooks" + ], + "imported_by": [], + "reachable_from_entries": [], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [], + "orphan": true + }, + { + "path": "web/js/components/preact/ToastContainer.jsx", + "ext": ".jsx", + "lines": 181, + "kind": "component", + "proposed_domain_owner": "shared-ui-primitives", + "imports": [ + "web/js/utils/date-utils.js" + ], + "external_imports": [ + "preact/compat", + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/BatchDeleteModal.jsx", + "web/js/components/preact/BatchDownloadModal.jsx", + "web/js/components/preact/DayNightControl.jsx", + "web/js/components/preact/DetectionOverlay.jsx", + "web/js/components/preact/EventRoutingView.jsx", + "web/js/components/preact/HLSVideoCell.jsx", + "web/js/components/preact/Header.jsx", + "web/js/components/preact/LiveView.jsx", + "web/js/components/preact/MSEVideoCell.jsx", + "web/js/components/preact/ManualRecordingButton.jsx", + "web/js/components/preact/MobileTileGestures.jsx", + "web/js/components/preact/PTZControls.jsx", + "web/js/components/preact/PictureInPictureButton.jsx", + "web/js/components/preact/RecordingsView.jsx", + "web/js/components/preact/SettingsView.jsx", + "web/js/components/preact/SetupWizard.jsx", + "web/js/components/preact/SnapshotManager.jsx", + "web/js/components/preact/StreamConfigModal.jsx", + "web/js/components/preact/StreamsView.jsx", + "web/js/components/preact/SystemView.jsx", + "web/js/components/preact/UI.jsx", + "web/js/components/preact/UsersView.jsx", + "web/js/components/preact/WebRTCVideoCell.jsx", + "web/js/components/preact/events/EventDestinationEditor.jsx", + "web/js/components/preact/events/EventRouteEditor.jsx", + "web/js/components/preact/fleet/BulkOrganizeModal.jsx", + "web/js/components/preact/fleet/CollectionManager.jsx", + "web/js/components/preact/fleet/OrganizationManager.jsx", + "web/js/components/preact/fleet/SavedViewsBar.jsx", + "web/js/components/preact/live/LiveBuildingPlan.jsx", + "web/js/components/preact/live/LiveOperatorNavigator.jsx", + "web/js/components/preact/lpr/PlatesView.jsx", + "web/js/components/preact/recordings/TagsOverlay.jsx", + "web/js/components/preact/recordings/recordingsAPI.jsx", + "web/js/components/preact/settings/GeneralTab.jsx", + "web/js/components/preact/settings/StorageTab.jsx", + "web/js/components/preact/settings/WorkspacesTab.jsx", + "web/js/components/preact/timeline/SpeedControls.jsx", + "web/js/components/preact/timeline/TimelineControls.jsx", + "web/js/components/preact/timeline/TimelinePage.jsx", + "web/js/components/preact/timeline/TimelinePlayer.jsx", + "web/js/components/preact/users/AccessPolicyModal.jsx", + "web/js/components/preact/users/ApiKeyModal.jsx", + "web/js/components/preact/users/AuditDecisionModes.jsx", + "web/js/components/preact/users/AuditHistoryModal.jsx", + "web/js/components/preact/users/RoleManagerModal.jsx", + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/investigation-page.jsx", + "web/js/pages/login-page.jsx", + "web/js/pages/plates-page.jsx", + "web/js/pages/recordings-page.jsx", + "web/js/pages/settings-page.jsx", + "web/js/pages/streams-page.jsx", + "web/js/pages/system-page.jsx", + "web/js/pages/timeline-page.jsx", + "web/js/pages/users-page.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/investigation-page.jsx", + "web/js/pages/login-page.jsx", + "web/js/pages/plates-page.jsx", + "web/js/pages/recordings-page.jsx", + "web/js/pages/settings-page.jsx", + "web/js/pages/streams-page.jsx", + "web/js/pages/system-page.jsx", + "web/js/pages/timeline-page.jsx", + "web/js/pages/users-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [ + "web/tests/recordingsAPI.spec.js" + ], + "consumed_by_other_domains": [ + "events", + "fleet", + "investigation", + "live", + "lpr", + "recordings", + "settings", + "shared-auth", + "shared-shell", + "streams", + "system-health", + "timeline", + "users-access" + ] + }, + { + "path": "web/js/components/preact/UI.jsx", + "ext": ".jsx", + "lines": 1279, + "kind": "component", + "proposed_domain_owner": "shared-ui-primitives", + "imports": [ + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/common/ModalDialog.jsx", + "web/js/components/preact/recordings/formatUtils.js", + "web/js/i18n.js", + "web/js/utils/date-utils.js", + "web/js/utils/recording-playback.js" + ], + "external_imports": [ + "dayjs", + "dayjs/plugin/customParseFormat", + "dayjs/plugin/utc", + "preact", + "preact/compat", + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/HLSVideoCell.jsx", + "web/js/components/preact/RecordingsView.jsx", + "web/js/components/preact/SnapshotManager.jsx", + "web/js/components/preact/WebRTCVideoCell.jsx", + "web/js/components/preact/timeline/TimelinePlayer.jsx", + "web/js/pages/recordings-page.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/recordings-page.jsx", + "web/js/pages/timeline-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "toast", + "modal" + ], + "ui_operation_ids": [ + "UI-095", + "UI-096", + "UI-097", + "UI-098" + ], + "tests": [], + "consumed_by_other_domains": [ + "live", + "recordings", + "timeline" + ] + }, + { + "path": "web/js/components/preact/UsersView.jsx", + "ext": ".jsx", + "lines": 525, + "kind": "component", + "proposed_domain_owner": "users-access", + "imports": [ + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/users/AccessPolicyModal.jsx", + "web/js/components/preact/users/AddUserModal.jsx", + "web/js/components/preact/users/ApiKeyModal.jsx", + "web/js/components/preact/users/AuditHistoryModal.jsx", + "web/js/components/preact/users/DeleteUserModal.jsx", + "web/js/components/preact/users/EditUserModal.jsx", + "web/js/components/preact/users/RoleManagerModal.jsx", + "web/js/components/preact/users/TotpSetupModal.jsx", + "web/js/components/preact/users/UsersTable.jsx", + "web/js/i18n.js", + "web/js/query-client.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/pages/users-page.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/users-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "toast", + "query" + ], + "ui_operation_ids": [ + "UI-099", + "UI-100", + "UI-101", + "UI-102", + "UI-103" + ], + "tests": [] + }, + { + "path": "web/js/components/preact/WebRTCVideoCell.jsx", + "ext": ".jsx", + "lines": 2165, + "kind": "component", + "proposed_domain_owner": "live", + "imports": [ + "web/js/components/preact/DayNightControl.jsx", + "web/js/components/preact/DetectionOverlay.jsx", + "web/js/components/preact/FisheyeEptzCanvas.jsx", + "web/js/components/preact/LiveTileStatus.jsx", + "web/js/components/preact/LoadingIndicator.jsx", + "web/js/components/preact/ManualRecordingButton.jsx", + "web/js/components/preact/MobileTileGestures.jsx", + "web/js/components/preact/PTZControls.jsx", + "web/js/components/preact/PictureInPictureButton.jsx", + "web/js/components/preact/SnapshotManager.jsx", + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/UI.jsx", + "web/js/components/preact/liveStreamPolicy.js", + "web/js/components/preact/recordings/formatUtils.js", + "web/js/components/preact/useAlwaysFullscreenOnTap.js", + "web/js/components/preact/useAutoRetry.js", + "web/js/components/preact/useVideoZoom.js", + "web/js/i18n.js", + "web/js/query-client.js", + "web/js/utils/date-utils.js", + "web/js/utils/eptz-config.js", + "web/js/utils/live-video-errors.js", + "web/js/utils/navigation-utils.js", + "web/js/utils/player-telemetry.js", + "web/js/utils/settings-utils.js", + "web/js/utils/stream-connection-gate.js", + "web/js/utils/webrtc-ice-gathering.js" + ], + "external_imports": [ + "preact/hooks", + "webrtc-adapter" + ], + "imported_by": [ + "web/js/components/preact/PlaybackTransportCell.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "toast", + "modal", + "query" + ], + "ui_operation_ids": [ + "UI-104", + "UI-105", + "UI-106", + "UI-107", + "UI-108" + ], + "tests": [], + "i18n_literal_baseline_key": "preact/WebRTCVideoCell.jsx", + "i18n_literal_baseline_count": 1 + }, + { + "path": "web/js/components/preact/ZoneEditor.jsx", + "ext": ".jsx", + "lines": 702, + "kind": "component", + "proposed_domain_owner": "streams", + "imports": [ + "web/js/components/preact/common/ModalDialog.jsx", + "web/js/i18n.js", + "web/js/utils/date-utils.js", + "web/js/utils/settings-utils.js", + "web/js/utils/zone-editor-geometry.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/StreamConfigModal.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/streams-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "modal", + "zone_editor" + ], + "ui_operation_ids": [ + "UI-109", + "UI-110", + "UI-111", + "UI-112" + ], + "tests": [] + }, + { + "path": "web/js/components/preact/common/Image.jsx", + "ext": ".jsx", + "lines": 90, + "kind": "component", + "proposed_domain_owner": "shared-ui-primitives", + "imports": [], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [], + "reachable_from_entries": [], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [], + "orphan": true + }, + { + "path": "web/js/components/preact/common/LanguageSelector.jsx", + "ext": ".jsx", + "lines": 73, + "kind": "component", + "proposed_domain_owner": "shared-i18n", + "imports": [ + "web/js/i18n.js" + ], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/Header.jsx", + "web/js/components/preact/LoginView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/investigation-page.jsx", + "web/js/pages/login-page.jsx", + "web/js/pages/plates-page.jsx", + "web/js/pages/recordings-page.jsx", + "web/js/pages/settings-page.jsx", + "web/js/pages/streams-page.jsx", + "web/js/pages/system-page.jsx", + "web/js/pages/timeline-page.jsx", + "web/js/pages/users-page.jsx" + ], + "shared_dependencies_used": [ + "i18n" + ], + "ui_operation_ids": [], + "tests": [], + "consumed_by_other_domains": [ + "shared-auth", + "shared-shell" + ] + }, + { + "path": "web/js/components/preact/common/ModalDialog.jsx", + "ext": ".jsx", + "lines": 283, + "kind": "component", + "proposed_domain_owner": "shared-ui-primitives", + "imports": [], + "external_imports": [ + "preact/compat", + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/EventRoutingView.jsx", + "web/js/components/preact/UI.jsx", + "web/js/components/preact/ZoneEditor.jsx", + "web/js/components/preact/fleet/CollectionManager.jsx", + "web/js/components/preact/fleet/OrganizationManager.jsx", + "web/js/components/preact/fleet/SavedViewsBar.jsx", + "web/js/components/preact/live/LiveBuildingPlan.jsx", + "web/js/components/preact/live/LiveOperatorNavigator.jsx", + "web/js/components/preact/lpr/PlatesView.jsx", + "web/js/components/preact/settings/StorageTab.jsx", + "web/js/components/preact/users/AccessPolicyModal.jsx", + "web/js/components/preact/users/ApiKeyModal.jsx", + "web/js/components/preact/users/RoleManagerModal.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/plates-page.jsx", + "web/js/pages/recordings-page.jsx", + "web/js/pages/settings-page.jsx", + "web/js/pages/streams-page.jsx", + "web/js/pages/timeline-page.jsx", + "web/js/pages/users-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [], + "consumed_by_other_domains": [ + "events", + "fleet", + "live", + "lpr", + "settings", + "streams", + "users-access" + ] + }, + { + "path": "web/js/components/preact/events/EventDestinationEditor.jsx", + "ext": ".jsx", + "lines": 195, + "kind": "component", + "proposed_domain_owner": "events", + "imports": [ + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/events/eventRouting.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/EventRoutingView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/streams-page.jsx" + ], + "shared_dependencies_used": [ + "toast" + ], + "ui_operation_ids": [], + "tests": [], + "i18n_literal_baseline_key": "preact/events/EventDestinationEditor.jsx", + "i18n_literal_baseline_count": 5 + }, + { + "path": "web/js/components/preact/events/EventRouteEditor.jsx", + "ext": ".jsx", + "lines": 332, + "kind": "component", + "proposed_domain_owner": "events", + "imports": [ + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/events/eventRouting.js", + "web/js/components/preact/fleet/CollectionSelectorBuilder.jsx" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/EventRoutingView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/streams-page.jsx" + ], + "shared_dependencies_used": [ + "toast" + ], + "ui_operation_ids": [], + "tests": [], + "i18n_literal_baseline_key": "preact/events/EventRouteEditor.jsx", + "i18n_literal_baseline_count": 3 + }, + { + "path": "web/js/components/preact/events/eventRouting.js", + "ext": ".js", + "lines": 389, + "kind": "module", + "proposed_domain_owner": "events", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/EventRoutingView.jsx", + "web/js/components/preact/events/EventDestinationEditor.jsx", + "web/js/components/preact/events/EventRouteEditor.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/streams-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [ + "web/tests/eventRouting.spec.js" + ] + }, + { + "path": "web/js/components/preact/fleet/BulkOrganizeModal.jsx", + "ext": ".jsx", + "lines": 130, + "kind": "component", + "proposed_domain_owner": "fleet", + "imports": [ + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/fleet/fleetOrganization.js", + "web/js/query-client.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/FleetView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/streams-page.jsx" + ], + "shared_dependencies_used": [ + "toast", + "query" + ], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/fleet/CollectionManager.jsx", + "ext": ".jsx", + "lines": 239, + "kind": "component", + "proposed_domain_owner": "fleet", + "imports": [ + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/common/ModalDialog.jsx", + "web/js/components/preact/fleet/CollectionSelectorBuilder.jsx", + "web/js/components/preact/fleet/StaticCollectionMembers.jsx", + "web/js/query-client.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/FleetView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/streams-page.jsx" + ], + "shared_dependencies_used": [ + "toast", + "modal", + "query" + ], + "ui_operation_ids": [ + "UI-113", + "UI-114", + "UI-115", + "UI-116", + "UI-117", + "UI-118" + ], + "tests": [] + }, + { + "path": "web/js/components/preact/fleet/CollectionSelectorBuilder.jsx", + "ext": ".jsx", + "lines": 367, + "kind": "component", + "proposed_domain_owner": "fleet", + "imports": [ + "web/js/components/preact/fleet/collectionSelector.js", + "web/js/components/preact/fleet/fleetOrganization.js", + "web/js/query-client.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/events/EventRouteEditor.jsx", + "web/js/components/preact/fleet/CollectionManager.jsx", + "web/js/components/preact/settings/StorageTab.jsx", + "web/js/components/preact/users/AccessPolicyModal.jsx", + "web/js/components/preact/users/ApiKeyModal.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/settings-page.jsx", + "web/js/pages/streams-page.jsx", + "web/js/pages/users-page.jsx" + ], + "shared_dependencies_used": [ + "query" + ], + "ui_operation_ids": [ + "UI-119", + "UI-120", + "UI-121" + ], + "tests": [], + "consumed_by_other_domains": [ + "events", + "settings", + "users-access" + ] + }, + { + "path": "web/js/components/preact/fleet/FleetFilters.jsx", + "ext": ".jsx", + "lines": 146, + "kind": "component", + "proposed_domain_owner": "fleet", + "imports": [ + "web/js/components/preact/fleet/fleetQuery.js" + ], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/FleetView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/streams-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/fleet/FleetTable.jsx", + "ext": ".jsx", + "lines": 193, + "kind": "component", + "proposed_domain_owner": "fleet", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/FleetView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/streams-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [], + "i18n_literal_baseline_key": "preact/fleet/FleetTable.jsx", + "i18n_literal_baseline_count": 1 + }, + { + "path": "web/js/components/preact/fleet/OrganizationManager.jsx", + "ext": ".jsx", + "lines": 322, + "kind": "component", + "proposed_domain_owner": "fleet", + "imports": [ + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/common/ModalDialog.jsx", + "web/js/components/preact/fleet/fleetOrganization.js", + "web/js/query-client.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/FleetView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/streams-page.jsx" + ], + "shared_dependencies_used": [ + "toast", + "modal", + "query" + ], + "ui_operation_ids": [ + "UI-122", + "UI-123", + "UI-124", + "UI-125", + "UI-126", + "UI-127", + "UI-128" + ], + "tests": [], + "i18n_literal_baseline_key": "preact/fleet/OrganizationManager.jsx", + "i18n_literal_baseline_count": 1 + }, + { + "path": "web/js/components/preact/fleet/SavedViewsBar.jsx", + "ext": ".jsx", + "lines": 133, + "kind": "component", + "proposed_domain_owner": "fleet", + "imports": [ + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/common/ModalDialog.jsx", + "web/js/components/preact/fleet/fleetQuery.js", + "web/js/query-client.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/FleetView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/streams-page.jsx" + ], + "shared_dependencies_used": [ + "toast", + "modal", + "query" + ], + "ui_operation_ids": [ + "UI-129", + "UI-130" + ], + "tests": [] + }, + { + "path": "web/js/components/preact/fleet/StaticCollectionMembers.jsx", + "ext": ".jsx", + "lines": 91, + "kind": "component", + "proposed_domain_owner": "fleet", + "imports": [ + "web/js/query-client.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/fleet/CollectionManager.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/streams-page.jsx" + ], + "shared_dependencies_used": [ + "query" + ], + "ui_operation_ids": [ + "UI-131" + ], + "tests": [] + }, + { + "path": "web/js/components/preact/fleet/collectionMembership.js", + "ext": ".js", + "lines": 62, + "kind": "module", + "proposed_domain_owner": "fleet", + "imports": [ + "web/js/query-client.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/LiveView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [ + "query" + ], + "ui_operation_ids": [ + "UI-132", + "UI-133" + ], + "tests": [ + "web/tests/collectionMembership.spec.js" + ], + "consumed_by_other_domains": [ + "live" + ] + }, + { + "path": "web/js/components/preact/fleet/collectionSelector.js", + "ext": ".js", + "lines": 102, + "kind": "module", + "proposed_domain_owner": "fleet", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/fleet/CollectionSelectorBuilder.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/settings-page.jsx", + "web/js/pages/streams-page.jsx", + "web/js/pages/users-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [ + "web/tests/collectionSelector.spec.js" + ] + }, + { + "path": "web/js/components/preact/fleet/fleetOrganization.js", + "ext": ".js", + "lines": 150, + "kind": "module", + "proposed_domain_owner": "fleet", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/FleetView.jsx", + "web/js/components/preact/fleet/BulkOrganizeModal.jsx", + "web/js/components/preact/fleet/CollectionSelectorBuilder.jsx", + "web/js/components/preact/fleet/OrganizationManager.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/settings-page.jsx", + "web/js/pages/streams-page.jsx", + "web/js/pages/users-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [ + "UI-134", + "UI-135", + "UI-136" + ], + "tests": [ + "web/tests/fleetOrganization.spec.js" + ] + }, + { + "path": "web/js/components/preact/fleet/fleetQuery.js", + "ext": ".js", + "lines": 219, + "kind": "module", + "proposed_domain_owner": "fleet", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/FleetView.jsx", + "web/js/components/preact/fleet/FleetFilters.jsx", + "web/js/components/preact/fleet/SavedViewsBar.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/streams-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [ + "web/tests/fleetQuery.spec.js" + ] + }, + { + "path": "web/js/components/preact/forcedPasswordChange.js", + "ext": ".js", + "lines": 7, + "kind": "module", + "proposed_domain_owner": "shared-auth", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/AuthGate.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/investigation-page.jsx", + "web/js/pages/plates-page.jsx", + "web/js/pages/recordings-page.jsx", + "web/js/pages/settings-page.jsx", + "web/js/pages/streams-page.jsx", + "web/js/pages/system-page.jsx", + "web/js/pages/timeline-page.jsx", + "web/js/pages/users-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [ + "web/tests/forcedPasswordChange.spec.js" + ] + }, + { + "path": "web/js/components/preact/fullscreenApi.js", + "ext": ".js", + "lines": 60, + "kind": "module", + "proposed_domain_owner": "live", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/FullscreenManager.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [ + "web/tests/fullscreenFallback.spec.js" + ] + }, + { + "path": "web/js/components/preact/health/Sparkline.jsx", + "ext": ".jsx", + "lines": 47, + "kind": "component", + "proposed_domain_owner": "system-health", + "imports": [], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/HealthView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/streams-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/investigation/InvestigationActions.jsx", + "ext": ".jsx", + "lines": 433, + "kind": "component", + "proposed_domain_owner": "investigation", + "imports": [ + "web/js/components/preact/investigation/investigationUtils.js", + "web/js/components/preact/recordings/formatUtils.js", + "web/js/query-client.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/investigation/InvestigationView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/investigation-page.jsx" + ], + "shared_dependencies_used": [ + "query" + ], + "ui_operation_ids": [ + "UI-137", + "UI-138", + "UI-139", + "UI-140", + "UI-141" + ], + "tests": [] + }, + { + "path": "web/js/components/preact/investigation/InvestigationBookmarks.jsx", + "ext": ".jsx", + "lines": 245, + "kind": "component", + "proposed_domain_owner": "investigation", + "imports": [ + "web/js/components/preact/investigation/investigationUtils.js", + "web/js/query-client.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/investigation/InvestigationView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/investigation-page.jsx" + ], + "shared_dependencies_used": [ + "query" + ], + "ui_operation_ids": [ + "UI-142", + "UI-143", + "UI-144" + ], + "tests": [] + }, + { + "path": "web/js/components/preact/investigation/InvestigationView.jsx", + "ext": ".jsx", + "lines": 1955, + "kind": "component", + "proposed_domain_owner": "investigation", + "imports": [ + "web/js/components/preact/FisheyeEptzCanvas.jsx", + "web/js/components/preact/LoadingIndicator.jsx", + "web/js/components/preact/investigation/InvestigationActions.jsx", + "web/js/components/preact/investigation/InvestigationBookmarks.jsx", + "web/js/components/preact/investigation/investigationUtils.js", + "web/js/components/preact/recordings/formatUtils.js", + "web/js/i18n.js", + "web/js/query-client.js", + "web/js/request-queue.js", + "web/js/utils/eptz-config.js", + "web/js/utils/recording-playback.js", + "web/js/utils/stream-summaries.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/pages/investigation-page.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/investigation-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "query" + ], + "ui_operation_ids": [ + "UI-145", + "UI-146", + "UI-147", + "UI-148", + "UI-149", + "UI-150" + ], + "tests": [ + "web/tests/open-issues-regressions.spec.js" + ] + }, + { + "path": "web/js/components/preact/investigation/investigationUtils.js", + "ext": ".js", + "lines": 376, + "kind": "module", + "proposed_domain_owner": "investigation", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/investigation/InvestigationActions.jsx", + "web/js/components/preact/investigation/InvestigationBookmarks.jsx", + "web/js/components/preact/investigation/InvestigationView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/investigation-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [ + "web/tests/investigationUtils.spec.js" + ] + }, + { + "path": "web/js/components/preact/live/LiveBuildingPlan.jsx", + "ext": ".jsx", + "lines": 1368, + "kind": "component", + "proposed_domain_owner": "live", + "imports": [ + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/common/ModalDialog.jsx", + "web/js/components/preact/live/PlanSketchControls.jsx", + "web/js/components/preact/live/PlanSketchLayer.jsx", + "web/js/components/preact/live/liveOperator.js", + "web/js/components/preact/live/planSketch.js", + "web/js/i18n.js", + "web/js/query-client.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/LiveView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "toast", + "modal", + "query" + ], + "ui_operation_ids": [ + "UI-151", + "UI-152", + "UI-153", + "UI-154", + "UI-155", + "UI-156", + "UI-157", + "UI-158", + "UI-159", + "UI-160" + ], + "tests": [] + }, + { + "path": "web/js/components/preact/live/LiveOperatorNavigator.jsx", + "ext": ".jsx", + "lines": 463, + "kind": "component", + "proposed_domain_owner": "live", + "imports": [ + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/common/ModalDialog.jsx", + "web/js/components/preact/live/liveOperator.js", + "web/js/i18n.js", + "web/js/query-client.js", + "web/js/utils/workspace-preferences.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/LiveView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "toast", + "modal", + "query" + ], + "ui_operation_ids": [ + "UI-161", + "UI-162", + "UI-163", + "UI-164", + "UI-165", + "UI-166", + "UI-167" + ], + "tests": [] + }, + { + "path": "web/js/components/preact/live/PlanSketchControls.jsx", + "ext": ".jsx", + "lines": 233, + "kind": "component", + "proposed_domain_owner": "live", + "imports": [ + "web/js/components/preact/live/planSketch.js" + ], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/live/LiveBuildingPlan.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/live/PlanSketchLayer.jsx", + "ext": ".jsx", + "lines": 161, + "kind": "component", + "proposed_domain_owner": "live", + "imports": [ + "web/js/components/preact/live/planSketch.js" + ], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/live/LiveBuildingPlan.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/live/cameraFavorites.js", + "ext": ".js", + "lines": 75, + "kind": "module", + "proposed_domain_owner": "live", + "imports": [ + "web/js/query-client.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/LiveView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [ + "query" + ], + "ui_operation_ids": [ + "UI-168", + "UI-169", + "UI-170" + ], + "tests": [ + "web/tests/cameraFavorites.spec.js" + ] + }, + { + "path": "web/js/components/preact/live/liveOperator.js", + "ext": ".js", + "lines": 253, + "kind": "module", + "proposed_domain_owner": "live", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/LiveView.jsx", + "web/js/components/preact/live/LiveBuildingPlan.jsx", + "web/js/components/preact/live/LiveOperatorNavigator.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [ + "web/tests/liveOperator.spec.js" + ] + }, + { + "path": "web/js/components/preact/live/planSketch.js", + "ext": ".js", + "lines": 320, + "kind": "module", + "proposed_domain_owner": "live", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/live/LiveBuildingPlan.jsx", + "web/js/components/preact/live/PlanSketchControls.jsx", + "web/js/components/preact/live/PlanSketchLayer.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [ + "web/tests/planSketch.spec.js" + ] + }, + { + "path": "web/js/components/preact/liveStreamPolicy.js", + "ext": ".js", + "lines": 21, + "kind": "module", + "proposed_domain_owner": "live", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/WebRTCVideoCell.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [ + "web/tests/open-issues-regressions.spec.js" + ] + }, + { + "path": "web/js/components/preact/liveTileStatus.js", + "ext": ".js", + "lines": 25, + "kind": "module", + "proposed_domain_owner": "live", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/LiveTileStatus.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [ + "web/tests/liveTileStatus.spec.js" + ] + }, + { + "path": "web/js/components/preact/lpr/PlatesView.jsx", + "ext": ".jsx", + "lines": 525, + "kind": "component", + "proposed_domain_owner": "lpr", + "imports": [ + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/common/ModalDialog.jsx", + "web/js/components/preact/lpr/lprSearch.js", + "web/js/fetch-utils.js", + "web/js/i18n.js", + "web/js/query-client.js", + "web/js/utils/stream-summaries.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/pages/plates-page.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/plates-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "auth_failure_redirect", + "toast", + "modal", + "query" + ], + "ui_operation_ids": [], + "tests": [ + "web/tests/platesView.spec.js" + ] + }, + { + "path": "web/js/components/preact/lpr/lprSearch.js", + "ext": ".js", + "lines": 247, + "kind": "module", + "proposed_domain_owner": "lpr", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/lpr/PlatesView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/plates-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [ + "UI-171", + "UI-172", + "UI-173" + ], + "tests": [ + "web/tests/lprSearch.spec.js" + ] + }, + { + "path": "web/js/components/preact/mobileLiveGestures.js", + "ext": ".js", + "lines": 47, + "kind": "module", + "proposed_domain_owner": "live", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/FullscreenManager.jsx", + "web/js/components/preact/LiveView.jsx", + "web/js/components/preact/MobileTileGestures.jsx", + "web/js/components/preact/usePullToRefresh.js" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/recordings-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [ + "web/tests/mobileLiveGestures.spec.js" + ] + }, + { + "path": "web/js/components/preact/pictureInPicture.js", + "ext": ".js", + "lines": 22, + "kind": "module", + "proposed_domain_owner": "live", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/PictureInPictureButton.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [ + "web/tests/pictureInPicture.spec.js" + ] + }, + { + "path": "web/js/components/preact/ptzActions.js", + "ext": ".js", + "lines": 46, + "kind": "module", + "proposed_domain_owner": "live", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/PTZControls.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [ + "UI-174", + "UI-175", + "UI-176", + "UI-177" + ], + "tests": [ + "web/tests/ptzActions.spec.js" + ] + }, + { + "path": "web/js/components/preact/recordings/ActiveFilters.jsx", + "ext": ".jsx", + "lines": 34, + "kind": "component", + "proposed_domain_owner": "recordings", + "imports": [ + "web/js/i18n.js" + ], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/RecordingsView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/recordings-page.jsx" + ], + "shared_dependencies_used": [ + "i18n" + ], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/recordings/FiltersSidebar.jsx", + "ext": ".jsx", + "lines": 488, + "kind": "component", + "proposed_domain_owner": "recordings", + "imports": [ + "web/js/components/preact/AsyncButton.jsx", + "web/js/components/preact/recordings/formatUtils.js", + "web/js/components/preact/recordings/lazyFilterOptions.js", + "web/js/components/preact/recordings/recordingsAPI.jsx", + "web/js/components/preact/recordings/urlUtils.js", + "web/js/i18n.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/RecordingsView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/recordings-page.jsx" + ], + "shared_dependencies_used": [ + "i18n" + ], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/recordings/PaginationControls.jsx", + "ext": ".jsx", + "lines": 79, + "kind": "component", + "proposed_domain_owner": "recordings", + "imports": [ + "web/js/i18n.js" + ], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/RecordingsView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/recordings-page.jsx" + ], + "shared_dependencies_used": [ + "i18n" + ], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/recordings/RecordingsGrid.jsx", + "ext": ".jsx", + "lines": 659, + "kind": "component", + "proposed_domain_owner": "recordings", + "imports": [ + "web/js/components/preact/recordings/TagsOverlay.jsx", + "web/js/components/preact/recordings/formatUtils.js", + "web/js/i18n.js", + "web/js/request-queue.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/RecordingsView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/recordings-page.jsx" + ], + "shared_dependencies_used": [ + "i18n" + ], + "ui_operation_ids": [ + "UI-178", + "UI-179", + "UI-180", + "UI-181" + ], + "tests": [] + }, + { + "path": "web/js/components/preact/recordings/RecordingsTable.jsx", + "ext": ".jsx", + "lines": 477, + "kind": "component", + "proposed_domain_owner": "recordings", + "imports": [ + "web/js/components/preact/recordings/TagsOverlay.jsx", + "web/js/components/preact/recordings/formatUtils.js", + "web/js/i18n.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/RecordingsView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/recordings-page.jsx" + ], + "shared_dependencies_used": [ + "i18n" + ], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/recordings/TagsOverlay.jsx", + "ext": ".jsx", + "lines": 331, + "kind": "component", + "proposed_domain_owner": "recordings", + "imports": [ + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/recordings/recordingsAPI.jsx", + "web/js/i18n.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/recordings/RecordingsGrid.jsx", + "web/js/components/preact/recordings/RecordingsTable.jsx", + "web/js/components/preact/timeline/TimelineControls.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/recordings-page.jsx", + "web/js/pages/timeline-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "toast" + ], + "ui_operation_ids": [], + "tests": [], + "consumed_by_other_domains": [ + "timeline" + ] + }, + { + "path": "web/js/components/preact/recordings/formatUtils.js", + "ext": ".js", + "lines": 177, + "kind": "module", + "proposed_domain_owner": "recordings", + "imports": [], + "external_imports": [ + "dayjs", + "dayjs/plugin/utc" + ], + "imported_by": [ + "web/js/components/preact/HLSVideoCell.jsx", + "web/js/components/preact/MSEVideoCell.jsx", + "web/js/components/preact/RecordingsView.jsx", + "web/js/components/preact/UI.jsx", + "web/js/components/preact/WebRTCVideoCell.jsx", + "web/js/components/preact/investigation/InvestigationActions.jsx", + "web/js/components/preact/investigation/InvestigationView.jsx", + "web/js/components/preact/recordings/FiltersSidebar.jsx", + "web/js/components/preact/recordings/RecordingsGrid.jsx", + "web/js/components/preact/recordings/RecordingsTable.jsx", + "web/js/components/preact/recordings/recordingsAPI.jsx", + "web/js/components/preact/recordings/urlUtils.js" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/investigation-page.jsx", + "web/js/pages/recordings-page.jsx", + "web/js/pages/timeline-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [ + "web/tests/open-issues-regressions.spec.js" + ], + "consumed_by_other_domains": [ + "investigation", + "live", + "shared-ui-primitives" + ] + }, + { + "path": "web/js/components/preact/recordings/lazyFilterOptions.js", + "ext": ".js", + "lines": 60, + "kind": "module", + "proposed_domain_owner": "recordings", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/recordings/FiltersSidebar.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/recordings-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [ + "web/tests/lazyFilterOptions.spec.js" + ] + }, + { + "path": "web/js/components/preact/recordings/recordingsAPI.jsx", + "ext": ".jsx", + "lines": 850, + "kind": "component", + "proposed_domain_owner": "recordings", + "imports": [ + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/recordings/formatUtils.js", + "web/js/components/preact/recordings/urlUtils.js", + "web/js/fetch-utils.js", + "web/js/i18n.js", + "web/js/query-client.js", + "web/js/utils/batch-delete-status.js", + "web/js/utils/recording-playback.js", + "web/js/utils/stream-summaries.js" + ], + "external_imports": [ + "dayjs", + "dayjs/plugin/customParseFormat", + "dayjs/plugin/utc" + ], + "imported_by": [ + "web/js/components/preact/RecordingsView.jsx", + "web/js/components/preact/recordings/FiltersSidebar.jsx", + "web/js/components/preact/recordings/TagsOverlay.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/recordings-page.jsx", + "web/js/pages/timeline-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "auth_failure_redirect", + "toast", + "query" + ], + "ui_operation_ids": [ + "UI-182", + "UI-183", + "UI-184", + "UI-185", + "UI-186", + "UI-187", + "UI-188", + "UI-189", + "UI-190", + "UI-191", + "UI-192", + "UI-193", + "UI-194", + "UI-195", + "UI-196", + "UI-197", + "UI-198", + "UI-199", + "UI-200", + "UI-201" + ], + "tests": [ + "web/tests/recordingsAPI.spec.js" + ] + }, + { + "path": "web/js/components/preact/recordings/urlUtils.js", + "ext": ".js", + "lines": 461, + "kind": "module", + "proposed_domain_owner": "recordings", + "imports": [ + "web/js/components/preact/recordings/formatUtils.js", + "web/js/utils/date-utils.js" + ], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/RecordingsView.jsx", + "web/js/components/preact/recordings/FiltersSidebar.jsx", + "web/js/components/preact/recordings/recordingsAPI.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/recordings-page.jsx", + "web/js/pages/timeline-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [ + "tests/integration/recordings-url-utils.ui.spec.ts", + "web/tests/recordingsUrlUtils.spec.js" + ] + }, + { + "path": "web/js/components/preact/settings/AdvancedTab.jsx", + "ext": ".jsx", + "lines": 191, + "kind": "component", + "proposed_domain_owner": "settings", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/SettingsView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/settings-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/settings/AppearanceTab.jsx", + "ext": ".jsx", + "lines": 70, + "kind": "component", + "proposed_domain_owner": "settings", + "imports": [ + "web/js/components/preact/ThemeCustomizer.jsx", + "web/js/utils/reduceMotion.js" + ], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/SettingsView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/settings-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/settings/AuthTab.jsx", + "ext": ".jsx", + "lines": 322, + "kind": "component", + "proposed_domain_owner": "settings", + "imports": [ + "web/js/components/preact/AsyncButton.jsx" + ], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/SettingsView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/settings-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [], + "i18n_literal_baseline_key": "preact/settings/AuthTab.jsx", + "i18n_literal_baseline_count": 2 + }, + { + "path": "web/js/components/preact/settings/DetectionTab.jsx", + "ext": ".jsx", + "lines": 214, + "kind": "component", + "proposed_domain_owner": "settings", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/SettingsView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/settings-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [], + "i18n_literal_baseline_key": "preact/settings/DetectionTab.jsx", + "i18n_literal_baseline_count": 2 + }, + { + "path": "web/js/components/preact/settings/GeneralTab.jsx", + "ext": ".jsx", + "lines": 217, + "kind": "component", + "proposed_domain_owner": "settings", + "imports": [ + "web/js/components/preact/ToastContainer.jsx" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/SettingsView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/settings-page.jsx" + ], + "shared_dependencies_used": [ + "toast" + ], + "ui_operation_ids": [ + "UI-202" + ], + "tests": [], + "i18n_literal_baseline_key": "preact/settings/GeneralTab.jsx", + "i18n_literal_baseline_count": 11 + }, + { + "path": "web/js/components/preact/settings/Go2rtcTab.jsx", + "ext": ".jsx", + "lines": 630, + "kind": "component", + "proposed_domain_owner": "settings", + "imports": [ + "web/js/query-client.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/SettingsView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/settings-page.jsx" + ], + "shared_dependencies_used": [ + "query" + ], + "ui_operation_ids": [ + "UI-203", + "UI-204", + "UI-205" + ], + "tests": [], + "i18n_literal_baseline_key": "preact/settings/Go2rtcTab.jsx", + "i18n_literal_baseline_count": 4 + }, + { + "path": "web/js/components/preact/settings/HealthTab.jsx", + "ext": ".jsx", + "lines": 289, + "kind": "component", + "proposed_domain_owner": "settings", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/SettingsView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/settings-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [ + "web/tests/systemHealthSettings.spec.js" + ] + }, + { + "path": "web/js/components/preact/settings/MqttTab.jsx", + "ext": ".jsx", + "lines": 265, + "kind": "component", + "proposed_domain_owner": "settings", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/SettingsView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/settings-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [], + "i18n_literal_baseline_key": "preact/settings/MqttTab.jsx", + "i18n_literal_baseline_count": 4 + }, + { + "path": "web/js/components/preact/settings/StorageTab.jsx", + "ext": ".jsx", + "lines": 967, + "kind": "component", + "proposed_domain_owner": "settings", + "imports": [ + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/common/ModalDialog.jsx", + "web/js/components/preact/fleet/CollectionSelectorBuilder.jsx", + "web/js/components/preact/settings/storageTargetRouting.js", + "web/js/query-client.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/SettingsView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/settings-page.jsx" + ], + "shared_dependencies_used": [ + "toast", + "modal", + "query" + ], + "ui_operation_ids": [ + "UI-206", + "UI-207", + "UI-208", + "UI-209", + "UI-210", + "UI-211", + "UI-212", + "UI-213", + "UI-214", + "UI-215", + "UI-216", + "UI-217", + "UI-218", + "UI-219", + "UI-220", + "UI-221", + "UI-222", + "UI-223", + "UI-224", + "UI-225", + "UI-226", + "UI-227", + "UI-228", + "UI-229", + "UI-230", + "UI-231", + "UI-232", + "UI-233" + ], + "tests": [], + "i18n_literal_baseline_key": "preact/settings/StorageTab.jsx", + "i18n_literal_baseline_count": 2 + }, + { + "path": "web/js/components/preact/settings/StreamsDefaultsTab.jsx", + "ext": ".jsx", + "lines": 108, + "kind": "component", + "proposed_domain_owner": "settings", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/SettingsView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/settings-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/settings/WorkspacesTab.jsx", + "ext": ".jsx", + "lines": 105, + "kind": "component", + "proposed_domain_owner": "settings", + "imports": [ + "web/js/components/preact/ToastContainer.jsx", + "web/js/query-client.js", + "web/js/utils/workspace-preferences.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/SettingsView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/settings-page.jsx" + ], + "shared_dependencies_used": [ + "toast", + "query" + ], + "ui_operation_ids": [ + "UI-234", + "UI-235" + ], + "tests": [] + }, + { + "path": "web/js/components/preact/settings/storageTargetRouting.js", + "ext": ".js", + "lines": 56, + "kind": "module", + "proposed_domain_owner": "settings", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/settings/StorageTab.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/settings-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [ + "web/tests/storageTargetRouting.spec.js" + ] + }, + { + "path": "web/js/components/preact/system/ClearLogsModal.jsx", + "ext": ".jsx", + "lines": 77, + "kind": "component", + "proposed_domain_owner": "system-health", + "imports": [ + "web/js/i18n.js" + ], + "external_imports": [ + "preact/compat", + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/SystemView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/system-page.jsx" + ], + "shared_dependencies_used": [ + "i18n" + ], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/system/HealthIncidentHistory.jsx", + "ext": ".jsx", + "lines": 89, + "kind": "component", + "proposed_domain_owner": "system-health", + "imports": [ + "web/js/components/preact/system/SystemUtils.js", + "web/js/i18n.js", + "web/js/query-client.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/system/SystemHealth.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/system-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "query" + ], + "ui_operation_ids": [ + "UI-236" + ], + "tests": [ + "web/tests/systemHealth.spec.js" + ] + }, + { + "path": "web/js/components/preact/system/LogsPoller.jsx", + "ext": ".jsx", + "lines": 140, + "kind": "component", + "proposed_domain_owner": "system-health", + "imports": [ + "web/js/components/preact/system/SystemUtils.js", + "web/js/fetch-utils.js", + "web/js/utils/date-utils.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/SystemView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/system-page.jsx" + ], + "shared_dependencies_used": [ + "auth_failure_redirect" + ], + "ui_operation_ids": [ + "UI-237" + ], + "tests": [], + "note_jsx_without_markup": true + }, + { + "path": "web/js/components/preact/system/LogsView.jsx", + "ext": ".jsx", + "lines": 160, + "kind": "component", + "proposed_domain_owner": "system-health", + "imports": [ + "web/js/i18n.js" + ], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/SystemView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/system-page.jsx" + ], + "shared_dependencies_used": [ + "i18n" + ], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/system/MemoryStorage.jsx", + "ext": ".jsx", + "lines": 84, + "kind": "component", + "proposed_domain_owner": "system-health", + "imports": [ + "web/js/i18n.js" + ], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/SystemView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/system-page.jsx" + ], + "shared_dependencies_used": [ + "i18n" + ], + "ui_operation_ids": [], + "tests": [ + "web/tests/systemHealth.spec.js" + ] + }, + { + "path": "web/js/components/preact/system/NetworkInfo.jsx", + "ext": ".jsx", + "lines": 40, + "kind": "component", + "proposed_domain_owner": "system-health", + "imports": [ + "web/js/i18n.js" + ], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/SystemView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/system-page.jsx" + ], + "shared_dependencies_used": [ + "i18n" + ], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/system/RestartModal.jsx", + "ext": ".jsx", + "lines": 212, + "kind": "component", + "proposed_domain_owner": "system-health", + "imports": [ + "web/js/components/preact/AsyncButton.jsx", + "web/js/i18n.js" + ], + "external_imports": [ + "preact/compat", + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/system/SystemControls.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/system-page.jsx" + ], + "shared_dependencies_used": [ + "i18n" + ], + "ui_operation_ids": [ + "UI-238", + "UI-239" + ], + "tests": [] + }, + { + "path": "web/js/components/preact/system/StorageHealth.jsx", + "ext": ".jsx", + "lines": 213, + "kind": "component", + "proposed_domain_owner": "system-health", + "imports": [ + "web/js/i18n.js", + "web/js/query-client.js", + "web/js/utils/date-utils.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/SystemView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/system-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "query" + ], + "ui_operation_ids": [ + "UI-240", + "UI-241" + ], + "tests": [] + }, + { + "path": "web/js/components/preact/system/StreamStorage.jsx", + "ext": ".jsx", + "lines": 151, + "kind": "component", + "proposed_domain_owner": "system-health", + "imports": [ + "web/js/i18n.js" + ], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/SystemView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/system-page.jsx" + ], + "shared_dependencies_used": [ + "i18n" + ], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/system/StreamsInfo.jsx", + "ext": ".jsx", + "lines": 37, + "kind": "component", + "proposed_domain_owner": "system-health", + "imports": [ + "web/js/i18n.js" + ], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/SystemView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/system-page.jsx" + ], + "shared_dependencies_used": [ + "i18n" + ], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/system/SystemControls.jsx", + "ext": ".jsx", + "lines": 65, + "kind": "component", + "proposed_domain_owner": "system-health", + "imports": [ + "web/js/components/preact/system/RestartModal.jsx", + "web/js/i18n.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/SystemView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/system-page.jsx" + ], + "shared_dependencies_used": [ + "i18n" + ], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/system/SystemHealth.jsx", + "ext": ".jsx", + "lines": 248, + "kind": "component", + "proposed_domain_owner": "system-health", + "imports": [ + "web/js/components/preact/system/HealthIncidentHistory.jsx", + "web/js/components/preact/system/SystemUtils.js", + "web/js/i18n.js", + "web/js/query-client.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/SystemView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/system-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "query" + ], + "ui_operation_ids": [ + "UI-242" + ], + "tests": [ + "web/tests/systemHealth.spec.js" + ] + }, + { + "path": "web/js/components/preact/system/SystemInfo.jsx", + "ext": ".jsx", + "lines": 54, + "kind": "component", + "proposed_domain_owner": "system-health", + "imports": [ + "web/js/i18n.js" + ], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/SystemView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/system-page.jsx" + ], + "shared_dependencies_used": [ + "i18n" + ], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/system/SystemUtils.js", + "ext": ".js", + "lines": 97, + "kind": "module", + "proposed_domain_owner": "system-health", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/SystemView.jsx", + "web/js/components/preact/system/HealthIncidentHistory.jsx", + "web/js/components/preact/system/LogsPoller.jsx", + "web/js/components/preact/system/SystemHealth.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/system-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [ + "web/tests/open-issues-regressions.spec.js" + ] + }, + { + "path": "web/js/components/preact/system/VersionsTable.jsx", + "ext": ".jsx", + "lines": 73, + "kind": "component", + "proposed_domain_owner": "system-health", + "imports": [ + "web/js/i18n.js" + ], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/SystemView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/system-page.jsx" + ], + "shared_dependencies_used": [ + "i18n" + ], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/system/WebServiceInfo.jsx", + "ext": ".jsx", + "lines": 107, + "kind": "component", + "proposed_domain_owner": "system-health", + "imports": [ + "web/js/i18n.js", + "web/js/query-client.js" + ], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/SystemView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/system-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "query" + ], + "ui_operation_ids": [ + "UI-243" + ], + "tests": [] + }, + { + "path": "web/js/components/preact/timeline/CalendarPicker.jsx", + "ext": ".jsx", + "lines": 224, + "kind": "component", + "proposed_domain_owner": "timeline", + "imports": [ + "web/js/i18n.js" + ], + "external_imports": [ + "dayjs", + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/timeline/TimelinePage.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/timeline-page.jsx" + ], + "shared_dependencies_used": [ + "i18n" + ], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/timeline/SpeedControls.jsx", + "ext": ".jsx", + "lines": 67, + "kind": "component", + "proposed_domain_owner": "timeline", + "imports": [ + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/timeline/TimelinePage.jsx", + "web/js/i18n.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/timeline/TimelinePlayer.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/timeline-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "toast" + ], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/timeline/TimelineControls.jsx", + "ext": ".jsx", + "lines": 418, + "kind": "component", + "proposed_domain_owner": "timeline", + "imports": [ + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/recordings/TagsOverlay.jsx", + "web/js/components/preact/timeline/TimelinePage.jsx", + "web/js/components/preact/timeline/timelineUtils.js", + "web/js/i18n.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/timeline/TimelinePage.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/timeline-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "toast" + ], + "ui_operation_ids": [ + "UI-244" + ], + "tests": [] + }, + { + "path": "web/js/components/preact/timeline/TimelineCursor.jsx", + "ext": ".jsx", + "lines": 490, + "kind": "component", + "proposed_domain_owner": "timeline", + "imports": [ + "web/js/components/preact/timeline/TimelinePage.jsx", + "web/js/components/preact/timeline/timelineUtils.js", + "web/js/i18n.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/timeline/TimelinePage.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/timeline-page.jsx" + ], + "shared_dependencies_used": [ + "i18n" + ], + "ui_operation_ids": [ + "UI-245", + "UI-246" + ], + "tests": [] + }, + { + "path": "web/js/components/preact/timeline/TimelinePage.jsx", + "ext": ".jsx", + "lines": 1679, + "kind": "component", + "proposed_domain_owner": "timeline", + "imports": [ + "web/js/components/preact/BatchDownloadModal.jsx", + "web/js/components/preact/LoadingIndicator.jsx", + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/timeline/CalendarPicker.jsx", + "web/js/components/preact/timeline/TimelineControls.jsx", + "web/js/components/preact/timeline/TimelineCursor.jsx", + "web/js/components/preact/timeline/TimelinePlayer.jsx", + "web/js/components/preact/timeline/TimelineRuler.jsx", + "web/js/components/preact/timeline/TimelineSegments.jsx", + "web/js/components/preact/timeline/timelineUtils.js", + "web/js/i18n.js", + "web/js/query-client.js", + "web/js/utils/date-utils.js", + "web/js/utils/reduceMotion.js", + "web/js/utils/stream-summaries.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/timeline/SpeedControls.jsx", + "web/js/components/preact/timeline/TimelineControls.jsx", + "web/js/components/preact/timeline/TimelineCursor.jsx", + "web/js/components/preact/timeline/TimelinePlayer.jsx", + "web/js/components/preact/timeline/TimelineRuler.jsx", + "web/js/components/preact/timeline/TimelineSegments.jsx", + "web/js/pages/timeline-page.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/timeline-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "toast", + "query" + ], + "ui_operation_ids": [ + "UI-247" + ], + "tests": [] + }, + { + "path": "web/js/components/preact/timeline/TimelinePlayer.jsx", + "ext": ".jsx", + "lines": 1108, + "kind": "component", + "proposed_domain_owner": "timeline", + "imports": [ + "web/js/components/preact/FisheyeEptzCanvas.jsx", + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/UI.jsx", + "web/js/components/preact/timeline/SpeedControls.jsx", + "web/js/components/preact/timeline/TimelinePage.jsx", + "web/js/components/preact/timeline/timelineUtils.js", + "web/js/i18n.js", + "web/js/utils/date-utils.js", + "web/js/utils/eptz-config.js", + "web/js/utils/recording-playback.js", + "web/js/utils/stream-summaries.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/timeline/TimelinePage.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/timeline-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "toast", + "modal" + ], + "ui_operation_ids": [ + "UI-248", + "UI-249", + "UI-250", + "UI-251", + "UI-252", + "UI-253" + ], + "tests": [] + }, + { + "path": "web/js/components/preact/timeline/TimelineRuler.jsx", + "ext": ".jsx", + "lines": 99, + "kind": "component", + "proposed_domain_owner": "timeline", + "imports": [ + "web/js/components/preact/timeline/TimelinePage.jsx", + "web/js/components/preact/timeline/timelineUtils.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/timeline/TimelinePage.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/timeline-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/timeline/TimelineSegments.jsx", + "ext": ".jsx", + "lines": 364, + "kind": "component", + "proposed_domain_owner": "timeline", + "imports": [ + "web/js/components/preact/timeline/TimelinePage.jsx", + "web/js/components/preact/timeline/timelineUtils.js", + "web/js/i18n.js", + "web/js/utils/date-utils.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/timeline/TimelinePage.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/timeline-page.jsx" + ], + "shared_dependencies_used": [ + "i18n" + ], + "ui_operation_ids": [ + "UI-254", + "UI-255" + ], + "tests": [] + }, + { + "path": "web/js/components/preact/timeline/timelineUtils.js", + "ext": ".js", + "lines": 650, + "kind": "module", + "proposed_domain_owner": "timeline", + "imports": [], + "external_imports": [ + "dayjs" + ], + "imported_by": [ + "web/js/components/preact/timeline/TimelineControls.jsx", + "web/js/components/preact/timeline/TimelineCursor.jsx", + "web/js/components/preact/timeline/TimelinePage.jsx", + "web/js/components/preact/timeline/TimelinePlayer.jsx", + "web/js/components/preact/timeline/TimelineRuler.jsx", + "web/js/components/preact/timeline/TimelineSegments.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/timeline-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [ + "UI-256" + ], + "tests": [ + "web/tests/lprSearch.spec.js", + "web/tests/open-issues-regressions.spec.js", + "web/tests/timeline-utils.spec.js" + ] + }, + { + "path": "web/js/components/preact/useAlwaysFullscreenOnTap.js", + "ext": ".js", + "lines": 53, + "kind": "hook", + "proposed_domain_owner": "live", + "imports": [], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/HLSVideoCell.jsx", + "web/js/components/preact/LiveView.jsx", + "web/js/components/preact/MSEVideoCell.jsx", + "web/js/components/preact/WebRTCVideoCell.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [ + "web/tests/liveFullscreenPreferences.spec.js" + ] + }, + { + "path": "web/js/components/preact/useAutoRetry.js", + "ext": ".js", + "lines": 96, + "kind": "hook", + "proposed_domain_owner": "live", + "imports": [], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/HLSVideoCell.jsx", + "web/js/components/preact/MSEVideoCell.jsx", + "web/js/components/preact/WebRTCVideoCell.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/useCameraOrder.js", + "ext": ".js", + "lines": 207, + "kind": "hook", + "proposed_domain_owner": "live", + "imports": [], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/LiveView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/useLiveDisplayMode.js", + "ext": ".js", + "lines": 80, + "kind": "hook", + "proposed_domain_owner": "live", + "imports": [], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/DetectionOverlay.jsx", + "web/js/components/preact/LiveDisplayModeToggle.jsx", + "web/js/components/preact/LiveView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [ + "web/tests/liveDisplayMode.spec.js" + ] + }, + { + "path": "web/js/components/preact/useLiveSlideshow.js", + "ext": ".js", + "lines": 156, + "kind": "hook", + "proposed_domain_owner": "live", + "imports": [], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/LiveSlideshowControl.jsx", + "web/js/components/preact/LiveView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [ + "web/tests/liveSlideshow.spec.js", + "web/tests/liveSlideshowHook.spec.js" + ] + }, + { + "path": "web/js/components/preact/usePullToRefresh.js", + "ext": ".js", + "lines": 62, + "kind": "hook", + "proposed_domain_owner": "live", + "imports": [ + "web/js/components/preact/mobileLiveGestures.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/LiveView.jsx", + "web/js/components/preact/RecordingsView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/recordings-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [], + "consumed_by_other_domains": [ + "recordings" + ] + }, + { + "path": "web/js/components/preact/useVideoZoom.js", + "ext": ".js", + "lines": 300, + "kind": "hook", + "proposed_domain_owner": "live", + "imports": [], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/HLSVideoCell.jsx", + "web/js/components/preact/MSEVideoCell.jsx", + "web/js/components/preact/WebRTCVideoCell.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/users/AccessPolicyModal.jsx", + "ext": ".jsx", + "lines": 307, + "kind": "component", + "proposed_domain_owner": "users-access", + "imports": [ + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/common/ModalDialog.jsx", + "web/js/components/preact/fleet/CollectionSelectorBuilder.jsx", + "web/js/components/preact/users/authorizationPolicy.js", + "web/js/i18n.js", + "web/js/query-client.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/UsersView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/users-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "toast", + "modal", + "query" + ], + "ui_operation_ids": [ + "UI-257", + "UI-258", + "UI-259", + "UI-260", + "UI-261", + "UI-262", + "UI-263", + "UI-264" + ], + "tests": [] + }, + { + "path": "web/js/components/preact/users/AddUserModal.jsx", + "ext": ".jsx", + "lines": 279, + "kind": "component", + "proposed_domain_owner": "users-access", + "imports": [ + "web/js/components/preact/AsyncButton.jsx", + "web/js/components/preact/users/UserRoles.js", + "web/js/i18n.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/UsersView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/users-page.jsx" + ], + "shared_dependencies_used": [ + "i18n" + ], + "ui_operation_ids": [], + "tests": [], + "i18n_literal_baseline_key": "preact/users/AddUserModal.jsx", + "i18n_literal_baseline_count": 1 + }, + { + "path": "web/js/components/preact/users/ApiKeyModal.jsx", + "ext": ".jsx", + "lines": 352, + "kind": "component", + "proposed_domain_owner": "users-access", + "imports": [ + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/common/ModalDialog.jsx", + "web/js/components/preact/fleet/CollectionSelectorBuilder.jsx", + "web/js/components/preact/users/apiTokenPolicy.js", + "web/js/components/preact/users/authorizationPolicy.js", + "web/js/i18n.js", + "web/js/query-client.js", + "web/js/utils/date-utils.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/UsersView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/users-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "toast", + "modal", + "query" + ], + "ui_operation_ids": [ + "UI-265", + "UI-266", + "UI-267", + "UI-268", + "UI-269", + "UI-270", + "UI-271", + "UI-272" + ], + "tests": [] + }, + { + "path": "web/js/components/preact/users/AuditDecisionModes.jsx", + "ext": ".jsx", + "lines": 93, + "kind": "component", + "proposed_domain_owner": "users-access", + "imports": [ + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/users/auditHistory.js", + "web/js/i18n.js", + "web/js/query-client.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/users/AuditHistoryModal.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/users-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "toast", + "query" + ], + "ui_operation_ids": [ + "UI-273" + ], + "tests": [] + }, + { + "path": "web/js/components/preact/users/AuditHistoryModal.jsx", + "ext": ".jsx", + "lines": 285, + "kind": "component", + "proposed_domain_owner": "users-access", + "imports": [ + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/users/AuditDecisionModes.jsx", + "web/js/components/preact/users/auditHistory.js", + "web/js/fetch-utils.js", + "web/js/i18n.js", + "web/js/query-client.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/UsersView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/users-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "auth_failure_redirect", + "toast", + "query" + ], + "ui_operation_ids": [ + "UI-274", + "UI-275", + "UI-276", + "UI-277" + ], + "tests": [], + "i18n_literal_baseline_key": "preact/users/AuditHistoryModal.jsx", + "i18n_literal_baseline_count": 1 + }, + { + "path": "web/js/components/preact/users/DeleteUserModal.jsx", + "ext": ".jsx", + "lines": 62, + "kind": "component", + "proposed_domain_owner": "users-access", + "imports": [ + "web/js/components/preact/AsyncButton.jsx", + "web/js/i18n.js" + ], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/UsersView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/users-page.jsx" + ], + "shared_dependencies_used": [ + "i18n" + ], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/users/EditUserModal.jsx", + "ext": ".jsx", + "lines": 313, + "kind": "component", + "proposed_domain_owner": "users-access", + "imports": [ + "web/js/components/preact/AsyncButton.jsx", + "web/js/components/preact/users/UserRoles.js", + "web/js/i18n.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/Header.jsx", + "web/js/components/preact/UsersView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/investigation-page.jsx", + "web/js/pages/plates-page.jsx", + "web/js/pages/recordings-page.jsx", + "web/js/pages/settings-page.jsx", + "web/js/pages/streams-page.jsx", + "web/js/pages/system-page.jsx", + "web/js/pages/timeline-page.jsx", + "web/js/pages/users-page.jsx" + ], + "shared_dependencies_used": [ + "i18n" + ], + "ui_operation_ids": [], + "tests": [], + "i18n_literal_baseline_key": "preact/users/EditUserModal.jsx", + "i18n_literal_baseline_count": 1, + "consumed_by_other_domains": [ + "shared-shell" + ] + }, + { + "path": "web/js/components/preact/users/RoleManagerModal.jsx", + "ext": ".jsx", + "lines": 219, + "kind": "component", + "proposed_domain_owner": "users-access", + "imports": [ + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/common/ModalDialog.jsx", + "web/js/components/preact/users/authorizationPolicy.js", + "web/js/i18n.js", + "web/js/query-client.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/UsersView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/users-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "toast", + "modal", + "query" + ], + "ui_operation_ids": [ + "UI-278", + "UI-279", + "UI-280", + "UI-281", + "UI-282" + ], + "tests": [] + }, + { + "path": "web/js/components/preact/users/TotpSetupModal.jsx", + "ext": ".jsx", + "lines": 238, + "kind": "component", + "proposed_domain_owner": "users-access", + "imports": [ + "web/js/components/preact/AsyncButton.jsx", + "web/js/i18n.js" + ], + "external_imports": [ + "preact/hooks", + "qrcode" + ], + "imported_by": [ + "web/js/components/preact/UsersView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/users-page.jsx" + ], + "shared_dependencies_used": [ + "i18n" + ], + "ui_operation_ids": [ + "UI-283", + "UI-284", + "UI-285", + "UI-286" + ], + "tests": [] + }, + { + "path": "web/js/components/preact/users/UserRoles.js", + "ext": ".js", + "lines": 14, + "kind": "module", + "proposed_domain_owner": "users-access", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/users/AddUserModal.jsx", + "web/js/components/preact/users/EditUserModal.jsx", + "web/js/components/preact/users/UsersTable.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/investigation-page.jsx", + "web/js/pages/plates-page.jsx", + "web/js/pages/recordings-page.jsx", + "web/js/pages/settings-page.jsx", + "web/js/pages/streams-page.jsx", + "web/js/pages/system-page.jsx", + "web/js/pages/timeline-page.jsx", + "web/js/pages/users-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/users/UsersTable.jsx", + "ext": ".jsx", + "lines": 234, + "kind": "component", + "proposed_domain_owner": "users-access", + "imports": [ + "web/js/components/preact/users/UserRoles.js", + "web/js/i18n.js", + "web/js/utils/date-utils.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/UsersView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/users-page.jsx" + ], + "shared_dependencies_used": [ + "i18n" + ], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/components/preact/users/apiTokenPolicy.js", + "ext": ".js", + "lines": 91, + "kind": "module", + "proposed_domain_owner": "users-access", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/users/ApiKeyModal.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/users-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [ + "web/tests/apiTokenPolicy.spec.js" + ] + }, + { + "path": "web/js/components/preact/users/auditHistory.js", + "ext": ".js", + "lines": 137, + "kind": "module", + "proposed_domain_owner": "users-access", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/users/AuditDecisionModes.jsx", + "web/js/components/preact/users/AuditHistoryModal.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/users-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [ + "web/tests/auditHistory.spec.js" + ] + }, + { + "path": "web/js/components/preact/users/authorizationPolicy.js", + "ext": ".js", + "lines": 115, + "kind": "module", + "proposed_domain_owner": "users-access", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/users/AccessPolicyModal.jsx", + "web/js/components/preact/users/ApiKeyModal.jsx", + "web/js/components/preact/users/RoleManagerModal.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/users-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [ + "web/tests/authorizationPolicy.spec.js" + ] + }, + { + "path": "web/js/fetch-utils.js", + "ext": ".js", + "lines": 211, + "kind": "module", + "proposed_domain_owner": "shared-data-query", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/lpr/PlatesView.jsx", + "web/js/components/preact/recordings/recordingsAPI.jsx", + "web/js/components/preact/system/LogsPoller.jsx", + "web/js/components/preact/users/AuditHistoryModal.jsx", + "web/js/query-client.js", + "web/js/utils/auth-utils.js", + "web/js/utils/settings-utils.js", + "web/js/utils/stream-summaries.js" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/investigation-page.jsx", + "web/js/pages/login-page.jsx", + "web/js/pages/plates-page.jsx", + "web/js/pages/recordings-page.jsx", + "web/js/pages/settings-page.jsx", + "web/js/pages/streams-page.jsx", + "web/js/pages/system-page.jsx", + "web/js/pages/timeline-page.jsx", + "web/js/pages/users-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [ + "web/tests/fetchUtils.spec.js", + "web/tests/go2rtcSettings.spec.js", + "web/tests/recordingsAPI.spec.js" + ], + "consumed_by_other_domains": [ + "lpr", + "recordings", + "shared-auth", + "shared-shell", + "streams", + "system-health", + "users-access" + ] + }, + { + "path": "web/js/hooks/useAsyncAction.js", + "ext": ".js", + "lines": 122, + "kind": "hook", + "proposed_domain_owner": "shared-data-query", + "imports": [], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/AsyncButton.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/investigation-page.jsx", + "web/js/pages/plates-page.jsx", + "web/js/pages/recordings-page.jsx", + "web/js/pages/settings-page.jsx", + "web/js/pages/streams-page.jsx", + "web/js/pages/system-page.jsx", + "web/js/pages/timeline-page.jsx", + "web/js/pages/users-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [ + "web/tests/useAsyncAction.spec.js" + ], + "consumed_by_other_domains": [ + "shared-ui-primitives" + ] + }, + { + "path": "web/js/i18n.js", + "ext": ".js", + "lines": 291, + "kind": "module", + "proposed_domain_owner": "shared-i18n", + "imports": [ + "web/js/utils/reduceMotion.js" + ], + "external_imports": [ + "preact/hooks" + ], + "imported_by": [ + "web/js/components/preact/AlwaysFullscreenToggle.jsx", + "web/js/components/preact/AsyncButton.jsx", + "web/js/components/preact/AuthGate.jsx", + "web/js/components/preact/BatchDeleteModal.jsx", + "web/js/components/preact/BatchDownloadModal.jsx", + "web/js/components/preact/DayNightControl.jsx", + "web/js/components/preact/DetectionOverlay.jsx", + "web/js/components/preact/EventRoutingView.jsx", + "web/js/components/preact/FisheyeEptzCanvas.jsx", + "web/js/components/preact/FleetView.jsx", + "web/js/components/preact/Footer.jsx", + "web/js/components/preact/GridPicker.jsx", + "web/js/components/preact/HLSVideoCell.jsx", + "web/js/components/preact/Header.jsx", + "web/js/components/preact/HealthView.jsx", + "web/js/components/preact/LiveDisplayModeToggle.jsx", + "web/js/components/preact/LiveSlideshowControl.jsx", + "web/js/components/preact/LiveTileStatus.jsx", + "web/js/components/preact/LiveView.jsx", + "web/js/components/preact/LoadingIndicator.jsx", + "web/js/components/preact/LoginView.jsx", + "web/js/components/preact/MSEVideoCell.jsx", + "web/js/components/preact/ManualRecordingButton.jsx", + "web/js/components/preact/MobileTileGestures.jsx", + "web/js/components/preact/PTZControls.jsx", + "web/js/components/preact/PictureInPictureButton.jsx", + "web/js/components/preact/PlaybackTransportCell.jsx", + "web/js/components/preact/RecordingsView.jsx", + "web/js/components/preact/SettingsView.jsx", + "web/js/components/preact/SetupWizard.jsx", + "web/js/components/preact/SnapshotManager.jsx", + "web/js/components/preact/StreamBulkActionModal.jsx", + "web/js/components/preact/StreamConfigModal.jsx", + "web/js/components/preact/StreamDeleteModal.jsx", + "web/js/components/preact/StreamsView.jsx", + "web/js/components/preact/SystemView.jsx", + "web/js/components/preact/ThemeCustomizer.jsx", + "web/js/components/preact/TileAudioButton.jsx", + "web/js/components/preact/UI.jsx", + "web/js/components/preact/UsersView.jsx", + "web/js/components/preact/WebRTCVideoCell.jsx", + "web/js/components/preact/ZoneEditor.jsx", + "web/js/components/preact/common/LanguageSelector.jsx", + "web/js/components/preact/investigation/InvestigationView.jsx", + "web/js/components/preact/live/LiveBuildingPlan.jsx", + "web/js/components/preact/live/LiveOperatorNavigator.jsx", + "web/js/components/preact/lpr/PlatesView.jsx", + "web/js/components/preact/recordings/ActiveFilters.jsx", + "web/js/components/preact/recordings/FiltersSidebar.jsx", + "web/js/components/preact/recordings/PaginationControls.jsx", + "web/js/components/preact/recordings/RecordingsGrid.jsx", + "web/js/components/preact/recordings/RecordingsTable.jsx", + "web/js/components/preact/recordings/TagsOverlay.jsx", + "web/js/components/preact/recordings/recordingsAPI.jsx", + "web/js/components/preact/system/ClearLogsModal.jsx", + "web/js/components/preact/system/HealthIncidentHistory.jsx", + "web/js/components/preact/system/LogsView.jsx", + "web/js/components/preact/system/MemoryStorage.jsx", + "web/js/components/preact/system/NetworkInfo.jsx", + "web/js/components/preact/system/RestartModal.jsx", + "web/js/components/preact/system/StorageHealth.jsx", + "web/js/components/preact/system/StreamStorage.jsx", + "web/js/components/preact/system/StreamsInfo.jsx", + "web/js/components/preact/system/SystemControls.jsx", + "web/js/components/preact/system/SystemHealth.jsx", + "web/js/components/preact/system/SystemInfo.jsx", + "web/js/components/preact/system/VersionsTable.jsx", + "web/js/components/preact/system/WebServiceInfo.jsx", + "web/js/components/preact/timeline/CalendarPicker.jsx", + "web/js/components/preact/timeline/SpeedControls.jsx", + "web/js/components/preact/timeline/TimelineControls.jsx", + "web/js/components/preact/timeline/TimelineCursor.jsx", + "web/js/components/preact/timeline/TimelinePage.jsx", + "web/js/components/preact/timeline/TimelinePlayer.jsx", + "web/js/components/preact/timeline/TimelineSegments.jsx", + "web/js/components/preact/users/AccessPolicyModal.jsx", + "web/js/components/preact/users/AddUserModal.jsx", + "web/js/components/preact/users/ApiKeyModal.jsx", + "web/js/components/preact/users/AuditDecisionModes.jsx", + "web/js/components/preact/users/AuditHistoryModal.jsx", + "web/js/components/preact/users/DeleteUserModal.jsx", + "web/js/components/preact/users/EditUserModal.jsx", + "web/js/components/preact/users/RoleManagerModal.jsx", + "web/js/components/preact/users/TotpSetupModal.jsx", + "web/js/components/preact/users/UsersTable.jsx", + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/investigation-page.jsx", + "web/js/pages/login-page.jsx", + "web/js/pages/plates-page.jsx", + "web/js/pages/recordings-page.jsx", + "web/js/pages/settings-page.jsx", + "web/js/pages/streams-page.jsx", + "web/js/pages/system-page.jsx", + "web/js/pages/timeline-page.jsx", + "web/js/pages/users-page.jsx", + "web/js/utils/batch-delete-status.js" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/investigation-page.jsx", + "web/js/pages/login-page.jsx", + "web/js/pages/plates-page.jsx", + "web/js/pages/recordings-page.jsx", + "web/js/pages/settings-page.jsx", + "web/js/pages/streams-page.jsx", + "web/js/pages/system-page.jsx", + "web/js/pages/timeline-page.jsx", + "web/js/pages/users-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [ + "web/tests/i18n.spec.js", + "web/tests/recordingsAPI.spec.js" + ], + "consumed_by_other_domains": [ + "events", + "fleet", + "investigation", + "live", + "lpr", + "recordings", + "settings", + "shared-auth", + "shared-shell", + "shared-ui-primitives", + "streams", + "system-health", + "timeline", + "users-access" + ] + }, + { + "path": "web/js/pages/hls-page.jsx", + "ext": ".jsx", + "lines": 101, + "kind": "page-entry", + "proposed_domain_owner": "live", + "imports": [ + "web/js/components/preact/AuthGate.jsx", + "web/js/components/preact/Footer.jsx", + "web/js/components/preact/Header.jsx", + "web/js/components/preact/LiveView.jsx", + "web/js/components/preact/ToastContainer.jsx", + "web/js/i18n.js", + "web/js/query-client.js", + "web/js/sentry.js", + "web/js/utils/auth-utils.js" + ], + "external_imports": [ + "preact", + "preact/hooks" + ], + "imported_by": [], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "auth", + "toast", + "query" + ], + "ui_operation_ids": [ + "UI-287" + ], + "tests": [] + }, + { + "path": "web/js/pages/index-page.jsx", + "ext": ".jsx", + "lines": 124, + "kind": "page-entry", + "proposed_domain_owner": "live", + "imports": [ + "web/js/components/preact/AuthGate.jsx", + "web/js/components/preact/Footer.jsx", + "web/js/components/preact/Header.jsx", + "web/js/components/preact/LiveView.jsx", + "web/js/components/preact/SetupWizard.jsx", + "web/js/components/preact/ToastContainer.jsx", + "web/js/i18n.js", + "web/js/query-client.js", + "web/js/sentry.js", + "web/js/utils/auth-utils.js", + "web/js/utils/live-view-url.js" + ], + "external_imports": [ + "preact", + "preact/hooks" + ], + "imported_by": [], + "reachable_from_entries": [ + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "auth", + "toast", + "query" + ], + "ui_operation_ids": [ + "UI-288", + "UI-289" + ], + "tests": [] + }, + { + "path": "web/js/pages/investigation-page.jsx", + "ext": ".jsx", + "lines": 29, + "kind": "page-entry", + "proposed_domain_owner": "investigation", + "imports": [ + "web/js/components/preact/AuthGate.jsx", + "web/js/components/preact/Footer.jsx", + "web/js/components/preact/Header.jsx", + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/investigation/InvestigationView.jsx", + "web/js/i18n.js", + "web/js/query-client.js", + "web/js/sentry.js", + "web/js/utils/auth-utils.js" + ], + "external_imports": [ + "preact" + ], + "imported_by": [], + "reachable_from_entries": [ + "web/js/pages/investigation-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "auth", + "toast", + "query" + ], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/pages/login-page.jsx", + "ext": ".jsx", + "lines": 29, + "kind": "page-entry", + "proposed_domain_owner": "shared-auth", + "imports": [ + "web/js/components/preact/LoginView.jsx", + "web/js/components/preact/ToastContainer.jsx", + "web/js/i18n.js", + "web/js/query-client.js", + "web/js/sentry.js" + ], + "external_imports": [ + "preact" + ], + "imported_by": [], + "reachable_from_entries": [ + "web/js/pages/login-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "toast", + "query" + ], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/pages/plates-page.jsx", + "ext": ".jsx", + "lines": 28, + "kind": "page-entry", + "proposed_domain_owner": "lpr", + "imports": [ + "web/js/components/preact/AuthGate.jsx", + "web/js/components/preact/Footer.jsx", + "web/js/components/preact/Header.jsx", + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/lpr/PlatesView.jsx", + "web/js/i18n.js", + "web/js/query-client.js", + "web/js/utils/auth-utils.js" + ], + "external_imports": [ + "preact" + ], + "imported_by": [], + "reachable_from_entries": [ + "web/js/pages/plates-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "auth", + "toast", + "query" + ], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/pages/recordings-page.jsx", + "ext": ".jsx", + "lines": 44, + "kind": "page-entry", + "proposed_domain_owner": "recordings", + "imports": [ + "web/js/components/preact/AuthGate.jsx", + "web/js/components/preact/BatchDeleteModal.jsx", + "web/js/components/preact/Footer.jsx", + "web/js/components/preact/Header.jsx", + "web/js/components/preact/RecordingsView.jsx", + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/UI.jsx", + "web/js/i18n.js", + "web/js/query-client.js", + "web/js/sentry.js", + "web/js/utils/auth-utils.js" + ], + "external_imports": [ + "preact" + ], + "imported_by": [], + "reachable_from_entries": [ + "web/js/pages/recordings-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "auth", + "toast", + "modal", + "query" + ], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/pages/settings-page.jsx", + "ext": ".jsx", + "lines": 39, + "kind": "page-entry", + "proposed_domain_owner": "settings", + "imports": [ + "web/js/components/preact/AuthGate.jsx", + "web/js/components/preact/Footer.jsx", + "web/js/components/preact/Header.jsx", + "web/js/components/preact/SettingsView.jsx", + "web/js/components/preact/ToastContainer.jsx", + "web/js/i18n.js", + "web/js/query-client.js", + "web/js/sentry.js", + "web/js/utils/auth-utils.js" + ], + "external_imports": [ + "preact" + ], + "imported_by": [], + "reachable_from_entries": [ + "web/js/pages/settings-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "auth", + "toast", + "query" + ], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/pages/streams-page.jsx", + "ext": ".jsx", + "lines": 39, + "kind": "page-entry", + "proposed_domain_owner": "streams", + "imports": [ + "web/js/components/preact/AuthGate.jsx", + "web/js/components/preact/Footer.jsx", + "web/js/components/preact/Header.jsx", + "web/js/components/preact/StreamsView.jsx", + "web/js/components/preact/ToastContainer.jsx", + "web/js/i18n.js", + "web/js/query-client.js", + "web/js/sentry.js", + "web/js/utils/auth-utils.js" + ], + "external_imports": [ + "preact" + ], + "imported_by": [], + "reachable_from_entries": [ + "web/js/pages/streams-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "auth", + "toast", + "query" + ], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/pages/system-page.jsx", + "ext": ".jsx", + "lines": 39, + "kind": "page-entry", + "proposed_domain_owner": "system-health", + "imports": [ + "web/js/components/preact/AuthGate.jsx", + "web/js/components/preact/Footer.jsx", + "web/js/components/preact/Header.jsx", + "web/js/components/preact/SystemView.jsx", + "web/js/components/preact/ToastContainer.jsx", + "web/js/i18n.js", + "web/js/query-client.js", + "web/js/sentry.js", + "web/js/utils/auth-utils.js" + ], + "external_imports": [ + "preact" + ], + "imported_by": [], + "reachable_from_entries": [ + "web/js/pages/system-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "auth", + "toast", + "query" + ], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/pages/timeline-page.jsx", + "ext": ".jsx", + "lines": 39, + "kind": "page-entry", + "proposed_domain_owner": "timeline", + "imports": [ + "web/js/components/preact/AuthGate.jsx", + "web/js/components/preact/Footer.jsx", + "web/js/components/preact/Header.jsx", + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/timeline/TimelinePage.jsx", + "web/js/i18n.js", + "web/js/query-client.js", + "web/js/sentry.js", + "web/js/utils/auth-utils.js" + ], + "external_imports": [ + "preact" + ], + "imported_by": [], + "reachable_from_entries": [ + "web/js/pages/timeline-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "auth", + "toast", + "query" + ], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/pages/users-page.jsx", + "ext": ".jsx", + "lines": 39, + "kind": "page-entry", + "proposed_domain_owner": "users-access", + "imports": [ + "web/js/components/preact/AuthGate.jsx", + "web/js/components/preact/Footer.jsx", + "web/js/components/preact/Header.jsx", + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/UsersView.jsx", + "web/js/i18n.js", + "web/js/query-client.js", + "web/js/sentry.js", + "web/js/utils/auth-utils.js" + ], + "external_imports": [ + "preact" + ], + "imported_by": [], + "reachable_from_entries": [ + "web/js/pages/users-page.jsx" + ], + "shared_dependencies_used": [ + "i18n", + "auth", + "toast", + "query" + ], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/query-client.js", + "ext": ".js", + "lines": 237, + "kind": "module", + "proposed_domain_owner": "shared-data-query", + "imports": [ + "web/js/fetch-utils.js" + ], + "external_imports": [ + "@preact-signals/query" + ], + "imported_by": [ + "web/js/components/preact/BatchDeleteModal.jsx", + "web/js/components/preact/EventRoutingView.jsx", + "web/js/components/preact/FleetView.jsx", + "web/js/components/preact/HLSVideoCell.jsx", + "web/js/components/preact/Header.jsx", + "web/js/components/preact/HealthView.jsx", + "web/js/components/preact/LiveView.jsx", + "web/js/components/preact/MSEVideoCell.jsx", + "web/js/components/preact/RecordingsView.jsx", + "web/js/components/preact/SettingsView.jsx", + "web/js/components/preact/StreamsView.jsx", + "web/js/components/preact/SystemView.jsx", + "web/js/components/preact/UsersView.jsx", + "web/js/components/preact/WebRTCVideoCell.jsx", + "web/js/components/preact/fleet/BulkOrganizeModal.jsx", + "web/js/components/preact/fleet/CollectionManager.jsx", + "web/js/components/preact/fleet/CollectionSelectorBuilder.jsx", + "web/js/components/preact/fleet/OrganizationManager.jsx", + "web/js/components/preact/fleet/SavedViewsBar.jsx", + "web/js/components/preact/fleet/StaticCollectionMembers.jsx", + "web/js/components/preact/fleet/collectionMembership.js", + "web/js/components/preact/investigation/InvestigationActions.jsx", + "web/js/components/preact/investigation/InvestigationBookmarks.jsx", + "web/js/components/preact/investigation/InvestigationView.jsx", + "web/js/components/preact/live/LiveBuildingPlan.jsx", + "web/js/components/preact/live/LiveOperatorNavigator.jsx", + "web/js/components/preact/live/cameraFavorites.js", + "web/js/components/preact/lpr/PlatesView.jsx", + "web/js/components/preact/recordings/recordingsAPI.jsx", + "web/js/components/preact/settings/Go2rtcTab.jsx", + "web/js/components/preact/settings/StorageTab.jsx", + "web/js/components/preact/settings/WorkspacesTab.jsx", + "web/js/components/preact/system/HealthIncidentHistory.jsx", + "web/js/components/preact/system/StorageHealth.jsx", + "web/js/components/preact/system/SystemHealth.jsx", + "web/js/components/preact/system/WebServiceInfo.jsx", + "web/js/components/preact/timeline/TimelinePage.jsx", + "web/js/components/preact/users/AccessPolicyModal.jsx", + "web/js/components/preact/users/ApiKeyModal.jsx", + "web/js/components/preact/users/AuditDecisionModes.jsx", + "web/js/components/preact/users/AuditHistoryModal.jsx", + "web/js/components/preact/users/RoleManagerModal.jsx", + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/investigation-page.jsx", + "web/js/pages/login-page.jsx", + "web/js/pages/plates-page.jsx", + "web/js/pages/recordings-page.jsx", + "web/js/pages/settings-page.jsx", + "web/js/pages/streams-page.jsx", + "web/js/pages/system-page.jsx", + "web/js/pages/timeline-page.jsx", + "web/js/pages/users-page.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/investigation-page.jsx", + "web/js/pages/login-page.jsx", + "web/js/pages/plates-page.jsx", + "web/js/pages/recordings-page.jsx", + "web/js/pages/settings-page.jsx", + "web/js/pages/streams-page.jsx", + "web/js/pages/system-page.jsx", + "web/js/pages/timeline-page.jsx", + "web/js/pages/users-page.jsx" + ], + "shared_dependencies_used": [ + "auth_failure_redirect" + ], + "ui_operation_ids": [], + "tests": [ + "web/tests/cameraFavorites.spec.js", + "web/tests/collectionMembership.spec.js", + "web/tests/recordingsAPI.spec.js" + ], + "consumed_by_other_domains": [ + "events", + "fleet", + "investigation", + "live", + "lpr", + "recordings", + "settings", + "shared-auth", + "shared-shell", + "streams", + "system-health", + "timeline", + "users-access" + ] + }, + { + "path": "web/js/request-queue.js", + "ext": ".js", + "lines": 284, + "kind": "module", + "proposed_domain_owner": "shared-data-query", + "imports": [ + "web/js/utils/date-utils.js" + ], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/RecordingsView.jsx", + "web/js/components/preact/investigation/InvestigationView.jsx", + "web/js/components/preact/recordings/RecordingsGrid.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/investigation-page.jsx", + "web/js/pages/recordings-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [ + "web/tests/requestQueue.spec.js" + ], + "consumed_by_other_domains": [ + "investigation", + "recordings" + ] + }, + { + "path": "web/js/sentry.js", + "ext": ".js", + "lines": 24, + "kind": "module", + "proposed_domain_owner": "shared-shell", + "imports": [], + "external_imports": [ + "@sentry/browser" + ], + "imported_by": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/investigation-page.jsx", + "web/js/pages/login-page.jsx", + "web/js/pages/recordings-page.jsx", + "web/js/pages/settings-page.jsx", + "web/js/pages/streams-page.jsx", + "web/js/pages/system-page.jsx", + "web/js/pages/timeline-page.jsx", + "web/js/pages/users-page.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/investigation-page.jsx", + "web/js/pages/login-page.jsx", + "web/js/pages/recordings-page.jsx", + "web/js/pages/settings-page.jsx", + "web/js/pages/streams-page.jsx", + "web/js/pages/system-page.jsx", + "web/js/pages/timeline-page.jsx", + "web/js/pages/users-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [], + "consumed_by_other_domains": [ + "investigation", + "live", + "recordings", + "settings", + "shared-auth", + "streams", + "system-health", + "timeline", + "users-access" + ] + }, + { + "path": "web/js/url-param-handler.js", + "ext": ".js", + "lines": 60, + "kind": "module", + "proposed_domain_owner": "shared-shell", + "imports": [], + "external_imports": [], + "imported_by": [], + "reachable_from_entries": [], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [], + "orphan": true + }, + { + "path": "web/js/utils/auth-utils.js", + "ext": ".js", + "lines": 237, + "kind": "module", + "proposed_domain_owner": "shared-auth", + "imports": [ + "web/js/fetch-utils.js" + ], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/AuthGate.jsx", + "web/js/components/preact/FleetView.jsx", + "web/js/components/preact/Header.jsx", + "web/js/components/preact/RecordingsView.jsx", + "web/js/components/preact/SettingsView.jsx", + "web/js/components/preact/StreamsView.jsx", + "web/js/components/preact/SystemView.jsx", + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/investigation-page.jsx", + "web/js/pages/plates-page.jsx", + "web/js/pages/recordings-page.jsx", + "web/js/pages/settings-page.jsx", + "web/js/pages/streams-page.jsx", + "web/js/pages/system-page.jsx", + "web/js/pages/timeline-page.jsx", + "web/js/pages/users-page.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/investigation-page.jsx", + "web/js/pages/plates-page.jsx", + "web/js/pages/recordings-page.jsx", + "web/js/pages/settings-page.jsx", + "web/js/pages/streams-page.jsx", + "web/js/pages/system-page.jsx", + "web/js/pages/timeline-page.jsx", + "web/js/pages/users-page.jsx" + ], + "shared_dependencies_used": [ + "auth_failure_redirect" + ], + "ui_operation_ids": [ + "UI-290" + ], + "tests": [], + "consumed_by_other_domains": [ + "fleet", + "investigation", + "live", + "lpr", + "recordings", + "settings", + "shared-shell", + "streams", + "system-health", + "timeline", + "users-access" + ] + }, + { + "path": "web/js/utils/batch-delete-status.js", + "ext": ".js", + "lines": 17, + "kind": "module", + "proposed_domain_owner": "recordings", + "imports": [ + "web/js/i18n.js" + ], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/BatchDeleteModal.jsx", + "web/js/components/preact/recordings/recordingsAPI.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/recordings-page.jsx", + "web/js/pages/timeline-page.jsx" + ], + "shared_dependencies_used": [ + "i18n" + ], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/utils/date-utils.js", + "ext": ".js", + "lines": 121, + "kind": "module", + "proposed_domain_owner": "shared-shell", + "imports": [], + "external_imports": [ + "dayjs", + "dayjs/plugin/utc" + ], + "imported_by": [ + "web/js/components/preact/DetectionOverlay.jsx", + "web/js/components/preact/Footer.jsx", + "web/js/components/preact/HLSVideoCell.jsx", + "web/js/components/preact/MSEVideoCell.jsx", + "web/js/components/preact/MobileTileGestures.jsx", + "web/js/components/preact/RecordingsView.jsx", + "web/js/components/preact/SettingsView.jsx", + "web/js/components/preact/SnapshotManager.jsx", + "web/js/components/preact/Toast.jsx", + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/UI.jsx", + "web/js/components/preact/WebRTCVideoCell.jsx", + "web/js/components/preact/ZoneEditor.jsx", + "web/js/components/preact/recordings/urlUtils.js", + "web/js/components/preact/system/LogsPoller.jsx", + "web/js/components/preact/system/StorageHealth.jsx", + "web/js/components/preact/timeline/TimelinePage.jsx", + "web/js/components/preact/timeline/TimelinePlayer.jsx", + "web/js/components/preact/timeline/TimelineSegments.jsx", + "web/js/components/preact/users/ApiKeyModal.jsx", + "web/js/components/preact/users/UsersTable.jsx", + "web/js/request-queue.js", + "web/js/utils/settings-utils.js" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/investigation-page.jsx", + "web/js/pages/login-page.jsx", + "web/js/pages/plates-page.jsx", + "web/js/pages/recordings-page.jsx", + "web/js/pages/settings-page.jsx", + "web/js/pages/streams-page.jsx", + "web/js/pages/system-page.jsx", + "web/js/pages/timeline-page.jsx", + "web/js/pages/users-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [], + "consumed_by_other_domains": [ + "live", + "recordings", + "settings", + "shared-data-query", + "shared-ui-primitives", + "streams", + "system-health", + "timeline", + "users-access" + ] + }, + { + "path": "web/js/utils/detection-polling.js", + "ext": ".js", + "lines": 57, + "kind": "module", + "proposed_domain_owner": "live", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/DetectionOverlay.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [ + "UI-291" + ], + "tests": [ + "web/tests/detectionPolling.spec.js" + ] + }, + { + "path": "web/js/utils/dom-utils.js", + "ext": ".js", + "lines": 271, + "kind": "module", + "proposed_domain_owner": "shared-shell", + "imports": [], + "external_imports": [], + "imported_by": [], + "reachable_from_entries": [], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [], + "orphan": true + }, + { + "path": "web/js/utils/eptz-config.js", + "ext": ".js", + "lines": 92, + "kind": "module", + "proposed_domain_owner": "streams", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/FisheyeEptzCanvas.jsx", + "web/js/components/preact/HLSVideoCell.jsx", + "web/js/components/preact/MSEVideoCell.jsx", + "web/js/components/preact/StreamsView.jsx", + "web/js/components/preact/WebRTCVideoCell.jsx", + "web/js/components/preact/investigation/InvestigationView.jsx", + "web/js/components/preact/timeline/TimelinePlayer.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/investigation-page.jsx", + "web/js/pages/streams-page.jsx", + "web/js/pages/timeline-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [ + "web/tests/eptzConfig.spec.js" + ], + "consumed_by_other_domains": [ + "investigation", + "live", + "timeline" + ] + }, + { + "path": "web/js/utils/live-video-errors.js", + "ext": ".js", + "lines": 36, + "kind": "module", + "proposed_domain_owner": "live", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/MSEVideoCell.jsx", + "web/js/components/preact/WebRTCVideoCell.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [ + "web/tests/liveVideoErrors.spec.js" + ] + }, + { + "path": "web/js/utils/live-view-url.js", + "ext": ".js", + "lines": 93, + "kind": "module", + "proposed_domain_owner": "live", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/LiveView.jsx", + "web/js/pages/index-page.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [ + "web/tests/liveViewTransportTabs.spec.js", + "web/tests/liveViewUrl.spec.js" + ] + }, + { + "path": "web/js/utils/navigation-utils.js", + "ext": ".js", + "lines": 28, + "kind": "module", + "proposed_domain_owner": "shared-shell", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/HLSVideoCell.jsx", + "web/js/components/preact/Header.jsx", + "web/js/components/preact/MSEVideoCell.jsx", + "web/js/components/preact/WebRTCVideoCell.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/investigation-page.jsx", + "web/js/pages/plates-page.jsx", + "web/js/pages/recordings-page.jsx", + "web/js/pages/settings-page.jsx", + "web/js/pages/streams-page.jsx", + "web/js/pages/system-page.jsx", + "web/js/pages/timeline-page.jsx", + "web/js/pages/users-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [ + "web/tests/navigation-utils.spec.js" + ], + "consumed_by_other_domains": [ + "live" + ] + }, + { + "path": "web/js/utils/playback-transport.js", + "ext": ".js", + "lines": 70, + "kind": "module", + "proposed_domain_owner": "live", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/PlaybackTransportCell.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [ + "web/tests/playbackTransport.spec.js" + ] + }, + { + "path": "web/js/utils/player-telemetry.js", + "ext": ".js", + "lines": 98, + "kind": "module", + "proposed_domain_owner": "live", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/HLSVideoCell.jsx", + "web/js/components/preact/MSEVideoCell.jsx", + "web/js/components/preact/WebRTCVideoCell.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [ + "UI-292", + "UI-293" + ], + "tests": [] + }, + { + "path": "web/js/utils/recording-playback.js", + "ext": ".js", + "lines": 90, + "kind": "module", + "proposed_domain_owner": "recordings", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/UI.jsx", + "web/js/components/preact/investigation/InvestigationView.jsx", + "web/js/components/preact/recordings/recordingsAPI.jsx", + "web/js/components/preact/timeline/TimelinePlayer.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/investigation-page.jsx", + "web/js/pages/recordings-page.jsx", + "web/js/pages/timeline-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [ + "web/tests/recordingPlayback.spec.js" + ], + "consumed_by_other_domains": [ + "investigation", + "shared-ui-primitives", + "timeline" + ] + }, + { + "path": "web/js/utils/reduceMotion.js", + "ext": ".js", + "lines": 123, + "kind": "module", + "proposed_domain_owner": "shared-shell", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/SettingsView.jsx", + "web/js/components/preact/settings/AppearanceTab.jsx", + "web/js/components/preact/timeline/TimelinePage.jsx", + "web/js/i18n.js" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/investigation-page.jsx", + "web/js/pages/login-page.jsx", + "web/js/pages/plates-page.jsx", + "web/js/pages/recordings-page.jsx", + "web/js/pages/settings-page.jsx", + "web/js/pages/streams-page.jsx", + "web/js/pages/system-page.jsx", + "web/js/pages/timeline-page.jsx", + "web/js/pages/users-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [], + "consumed_by_other_domains": [ + "settings", + "shared-i18n", + "timeline" + ] + }, + { + "path": "web/js/utils/settings-utils.js", + "ext": ".js", + "lines": 238, + "kind": "module", + "proposed_domain_owner": "shared-shell", + "imports": [ + "web/js/fetch-utils.js", + "web/js/utils/date-utils.js" + ], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/HLSVideoCell.jsx", + "web/js/components/preact/Header.jsx", + "web/js/components/preact/LiveView.jsx", + "web/js/components/preact/MSEVideoCell.jsx", + "web/js/components/preact/WebRTCVideoCell.jsx", + "web/js/components/preact/ZoneEditor.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/investigation-page.jsx", + "web/js/pages/plates-page.jsx", + "web/js/pages/recordings-page.jsx", + "web/js/pages/settings-page.jsx", + "web/js/pages/streams-page.jsx", + "web/js/pages/system-page.jsx", + "web/js/pages/timeline-page.jsx", + "web/js/pages/users-page.jsx" + ], + "shared_dependencies_used": [ + "auth_failure_redirect" + ], + "ui_operation_ids": [ + "UI-294" + ], + "tests": [ + "web/tests/go2rtcSettings.spec.js" + ], + "consumed_by_other_domains": [ + "live", + "streams" + ] + }, + { + "path": "web/js/utils/stream-availability.js", + "ext": ".js", + "lines": 23, + "kind": "module", + "proposed_domain_owner": "streams", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/StreamsView.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/streams-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [ + "web/tests/streamAvailability.spec.js" + ] + }, + { + "path": "web/js/utils/stream-connection-gate.js", + "ext": ".js", + "lines": 194, + "kind": "module", + "proposed_domain_owner": "live", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/HLSVideoCell.jsx", + "web/js/components/preact/MSEVideoCell.jsx", + "web/js/components/preact/WebRTCVideoCell.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/utils/stream-summaries.js", + "ext": ".js", + "lines": 57, + "kind": "module", + "proposed_domain_owner": "streams", + "imports": [ + "web/js/fetch-utils.js" + ], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/LiveView.jsx", + "web/js/components/preact/investigation/InvestigationView.jsx", + "web/js/components/preact/lpr/PlatesView.jsx", + "web/js/components/preact/recordings/recordingsAPI.jsx", + "web/js/components/preact/timeline/TimelinePage.jsx", + "web/js/components/preact/timeline/TimelinePlayer.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/investigation-page.jsx", + "web/js/pages/plates-page.jsx", + "web/js/pages/recordings-page.jsx", + "web/js/pages/timeline-page.jsx" + ], + "shared_dependencies_used": [ + "auth_failure_redirect" + ], + "ui_operation_ids": [ + "UI-295" + ], + "tests": [ + "web/tests/streamSummaries.spec.js" + ], + "consumed_by_other_domains": [ + "investigation", + "live", + "lpr", + "recordings", + "timeline" + ] + }, + { + "path": "web/js/utils/theme-init.js", + "ext": ".js", + "lines": 165, + "kind": "module", + "proposed_domain_owner": "shared-shell", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/ThemeCustomizer.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/settings-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [] + }, + { + "path": "web/js/utils/url-utils.js", + "ext": ".js", + "lines": 88, + "kind": "module", + "proposed_domain_owner": "shared-shell", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/StreamConfigModal.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/streams-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [], + "consumed_by_other_domains": [ + "streams" + ] + }, + { + "path": "web/js/utils/webrtc-ice-gathering.js", + "ext": ".js", + "lines": 78, + "kind": "module", + "proposed_domain_owner": "live", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/WebRTCVideoCell.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [ + "web/tests/webrtcIceGathering.spec.js" + ] + }, + { + "path": "web/js/utils/workspace-preferences.js", + "ext": ".js", + "lines": 24, + "kind": "module", + "proposed_domain_owner": "shared-shell", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/live/LiveOperatorNavigator.jsx", + "web/js/components/preact/settings/WorkspacesTab.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/hls-page.jsx", + "web/js/pages/index-page.jsx", + "web/js/pages/settings-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [ + "web/tests/workspacePreferences.spec.js" + ], + "consumed_by_other_domains": [ + "live", + "settings" + ] + }, + { + "path": "web/js/utils/zone-editor-geometry.js", + "ext": ".js", + "lines": 112, + "kind": "module", + "proposed_domain_owner": "streams", + "imports": [], + "external_imports": [], + "imported_by": [ + "web/js/components/preact/ZoneEditor.jsx" + ], + "reachable_from_entries": [ + "web/js/pages/streams-page.jsx" + ], + "shared_dependencies_used": [], + "ui_operation_ids": [], + "tests": [ + "web/tests/zoneEditorGeometry.spec.js" + ] + } + ], + "orphans": { + "paths": [ + "web/js/components/preact/Toast.jsx", + "web/js/components/preact/common/Image.jsx", + "web/js/url-param-handler.js", + "web/js/utils/dom-utils.js" + ], + "note": "Not reachable from any HTML entry via static/dynamic imports. Toast.jsx has no importers at all." + }, + "jsx_files_without_markup": [ + "web/js/components/preact/system/LogsPoller.jsx" + ], + "tests": [ + { + "path": "tests/integration/fixtures/recording-media.ts", + "kind": "playwright-fixture", + "imports_app_modules": [] + }, + { + "path": "tests/integration/fixtures/recording.mp4", + "kind": "playwright-fixture" + }, + { + "path": "tests/integration/fixtures/test-fixtures.ts", + "kind": "playwright-fixture", + "imports_app_modules": [], + "api_paths_exercised": [ + "/api/auth/login", + "/api/auth/logout", + "/api/auth/users", + "/api/auth/verify", + "/api/recordings", + "/api/settings", + "/api/setup/status", + "/api/streams", + "/api/system", + "/logout" + ] + }, + { + "path": "tests/integration/global-setup.ts", + "kind": "playwright-support", + "imports_app_modules": [], + "api_paths_exercised": [ + "/go2rtc", + "go2rtc/go2rtc", + "http://localhost:{}/api/auth/users", + "http://localhost:{}/api/setup/status", + "http://localhost:{}/api/streams", + "http://localhost:{}/api/system", + "{}/api/streams", + "{}/go2rtc" + ] + }, + { + "path": "tests/integration/global-teardown.ts", + "kind": "playwright-support", + "imports_app_modules": [] + }, + { + "path": "tests/integration/go2rtc.api.spec.ts", + "kind": "playwright-api", + "imports_app_modules": [], + "api_paths_exercised": [ + "/api/streams", + "/api/webrtc", + "/go2rtc", + "GET /api/streams returns registered streams", + "GET /api/webrtc endpoint is accessible" + ] + }, + { + "path": "tests/integration/lightnvr.api.spec.ts", + "kind": "playwright-api", + "imports_app_modules": [], + "api_paths_exercised": [ + "/api/recordings", + "/api/settings", + "/api/streams", + "/api/streams/playwright_get_test", + "/api/system", + "/api/system/info", + "GET /api/recordings returns recording list", + "GET /api/settings returns settings", + "GET /api/streams returns stream list", + "GET /api/streams/{name} returns stream details", + "GET /api/system/info returns system info and version summary", + "POST /api/streams creates a new stream" + ] + }, + { + "path": "tests/integration/lightnvr.ui.spec.ts", + "kind": "playwright-ui", + "imports_app_modules": [], + "api_paths_exercised": [ + "**/api/auth/login/config" + ] + }, + { + "path": "tests/integration/pages/BasePage.ts", + "kind": "playwright-page-object", + "imports_app_modules": [], + "api_paths_exercised": [ + "/logout" + ] + }, + { + "path": "tests/integration/pages/LiveViewPage.ts", + "kind": "playwright-page-object", + "imports_app_modules": [] + }, + { + "path": "tests/integration/pages/LoginPage.ts", + "kind": "playwright-page-object", + "imports_app_modules": [] + }, + { + "path": "tests/integration/pages/RecordingsPage.ts", + "kind": "playwright-page-object", + "imports_app_modules": [] + }, + { + "path": "tests/integration/pages/SettingsPage.ts", + "kind": "playwright-page-object", + "imports_app_modules": [] + }, + { + "path": "tests/integration/pages/StreamsPage.ts", + "kind": "playwright-page-object", + "imports_app_modules": [], + "api_paths_exercised": [ + "/api/streams" + ] + }, + { + "path": "tests/integration/pages/SystemPage.ts", + "kind": "playwright-page-object", + "imports_app_modules": [] + }, + { + "path": "tests/integration/pages/TimelinePage.ts", + "kind": "playwright-page-object", + "imports_app_modules": [] + }, + { + "path": "tests/integration/pages/UsersPage.ts", + "kind": "playwright-page-object", + "imports_app_modules": [] + }, + { + "path": "tests/integration/pages/index.ts", + "kind": "playwright-page-object", + "imports_app_modules": [] + }, + { + "path": "tests/integration/recordings-url-utils.ui.spec.ts", + "kind": "playwright-ui", + "imports_app_modules": [ + "web/js/components/preact/recordings/urlUtils.js" + ] + }, + { + "path": "tests/integration/specs/api-tokens.ui.spec.ts", + "kind": "playwright-ui", + "imports_app_modules": [], + "api_paths_exercised": [ + "**/api/**", + "/api/auth/users", + "/api/authorization/actions", + "/api/authorization/users/1/tokens", + "/api/camera-collections", + "/api/camera-tags", + "/api/locations" + ] + }, + { + "path": "tests/integration/specs/auth.api.spec.ts", + "kind": "playwright-api", + "imports_app_modules": [], + "api_paths_exercised": [ + "/api/auth/login", + "/api/auth/logout", + "/api/auth/users", + "/api/auth/users/{}", + "/api/auth/users/{}/api-key", + "/api/auth/users/{}/login-lockout/clear", + "/api/auth/verify", + "/api/system", + "DELETE /api/auth/users/{id} deletes user", + "GET /api/auth/users returns user list", + "GET /api/auth/users/{id} returns user details", + "GET /api/auth/verify checks session", + "POST /api/auth/login with invalid credentials", + "POST /api/auth/login with valid credentials", + "POST /api/auth/logout ends session", + "POST /api/auth/users creates new user", + "POST /api/auth/users stores bare IP login restrictions as single-host CIDRs", + "POST /api/auth/users/{id}/api-key generates API key", + "POST /api/auth/users/{id}/login-lockout/clear clears a user login lockout", + "PUT /api/auth/users/{id} updates user" + ] + }, + { + "path": "tests/integration/specs/auth.ui.spec.ts", + "kind": "playwright-ui", + "imports_app_modules": [] + }, + { + "path": "tests/integration/specs/camera-favorites.ui.spec.ts", + "kind": "playwright-ui", + "imports_app_modules": [], + "api_paths_exercised": [ + "**/api/**", + "**/go2rtc/api/ws*", + "/api/auth/verify", + "/api/camera-collections", + "/api/camera-favorites", + "/api/client-config", + "/api/fleet/cameras/query", + "/api/setup/status", + "/api/streams" + ] + }, + { + "path": "tests/integration/specs/comprehensive.api.spec.ts", + "kind": "playwright-api", + "imports_app_modules": [], + "api_paths_exercised": [ + "/api/health", + "/api/recordings", + "/api/recordings/protected", + "/api/settings", + "/api/streams", + "/api/streams/test", + "/api/streams/{}", + "/api/system", + "/api/system/logs", + "/api/system/status", + "DELETE /api/streams/{name} deletes stream", + "GET /api/health returns health status", + "GET /api/recordings returns recording list", + "GET /api/recordings with pagination", + "GET /api/recordings/protected returns protected count", + "GET /api/settings returns all settings", + "GET /api/streams returns stream list", + "GET /api/streams/{name} returns stream details", + "GET /api/system returns complete system info", + "GET /api/system/logs returns logs", + "GET /api/system/status returns status", + "POST /api/streams creates a new stream", + "POST /api/streams/test tests stream connection", + "PUT /api/streams/{name} updates stream" + ] + }, + { + "path": "tests/integration/specs/detection-overlay-requests.ui.spec.ts", + "kind": "playwright-ui", + "imports_app_modules": [], + "api_paths_exercised": [ + "**/api/**", + "**/go2rtc/api/ws*", + "/api/auth/verify", + "/api/client-config", + "/api/detection/results", + "/api/detection/results/cam_a", + "/api/setup/status", + "/api/streams" + ] + }, + { + "path": "tests/integration/specs/health-monitoring-state.ui.spec.ts", + "kind": "playwright-ui", + "imports_app_modules": [], + "api_paths_exercised": [ + "**/api/**", + "/api/auth/verify", + "/api/client-config", + "/api/system/health", + "/api/system/health/incidents", + "/api/system/info" + ] + }, + { + "path": "tests/integration/specs/investigation.ui.spec.ts", + "kind": "playwright-ui", + "imports_app_modules": [], + "api_paths_exercised": [ + "**/api/investigations/search", + "**/api/investigations/timeline", + "**/api/recordings/play/568*", + "**/api/streams" + ] + }, + { + "path": "tests/integration/specs/live-inventory-refresh.ui.spec.ts", + "kind": "playwright-ui", + "imports_app_modules": [], + "api_paths_exercised": [ + "**/api/**", + "**/go2rtc/api/ws*", + "/api/auth/verify", + "/api/client-config", + "/api/setup/status", + "/api/streams" + ] + }, + { + "path": "tests/integration/specs/liveview.ui.spec.ts", + "kind": "playwright-ui", + "imports_app_modules": [], + "api_paths_exercised": [ + "**/api/stream.m3u8*", + "**/api/webrtc", + "**/hls/**", + "/api/streams" + ] + }, + { + "path": "tests/integration/specs/recordings.ui.spec.ts", + "kind": "playwright-ui", + "imports_app_modules": [], + "api_paths_exercised": [ + "**/api/client-config*", + "**/api/detection/results/cam1", + "**/api/recordings", + "**/api/recordings/601", + "**/api/recordings/602", + "**/api/recordings/detection-labels*", + "**/api/recordings/play/601*", + "**/api/recordings/play/602*", + "**/api/recordings/tags*", + "**/api/recordings/thumbnail/602/**", + "**/api/settings*", + "**/api/streams*", + "/api/recordings/play/601" + ] + }, + { + "path": "tests/integration/specs/roles.ui.spec.ts", + "kind": "playwright-ui", + "imports_app_modules": [], + "api_paths_exercised": [ + "/api/auth/users" + ] + }, + { + "path": "tests/integration/specs/settings.ui.spec.ts", + "kind": "playwright-ui", + "imports_app_modules": [] + }, + { + "path": "tests/integration/specs/stream-snapshot-requests.ui.spec.ts", + "kind": "playwright-ui", + "imports_app_modules": [], + "api_paths_exercised": [ + "**/api/**", + "/api/auth/verify", + "/api/client-config", + "/api/detection/models", + "/api/streams", + "/api/streams/cam_a", + "/go2rtc/api/frame.jpeg" + ] + }, + { + "path": "tests/integration/specs/streams.ui.spec.ts", + "kind": "playwright-ui", + "imports_app_modules": [], + "api_paths_exercised": [ + "**/api/detection/models", + "**/api/streams/{}/full", + "**/go2rtc/api/frame.jpeg*", + "{}/api/streams/{}", + "{}/api/system" + ] + }, + { + "path": "tests/integration/specs/substream-fullscreen.ui.spec.ts", + "kind": "playwright-ui", + "imports_app_modules": [], + "api_paths_exercised": [ + "**/go2rtc/api/frame.jpeg*", + "**/go2rtc/api/streams*", + "**/hls/**" + ] + }, + { + "path": "tests/integration/specs/system-health-mqtt.api.spec.ts", + "kind": "playwright-api", + "imports_app_modules": [], + "api_paths_exercised": [ + "/api/event-destinations", + "/api/event-destinations/{}", + "/api/event-routes", + "/api/event-routes/preview", + "/api/event-routes/{}", + "/api/metrics", + "/api/system/health" + ] + }, + { + "path": "tests/integration/specs/system-health-settings.ui.spec.ts", + "kind": "playwright-ui", + "imports_app_modules": [], + "api_paths_exercised": [ + "**/api/auth/sessions", + "**/api/auth/trusted-devices", + "**/api/settings" + ] + }, + { + "path": "tests/integration/specs/system-health.api.spec.ts", + "kind": "playwright-api", + "imports_app_modules": [], + "api_paths_exercised": [ + "/api/health", + "/api/metrics", + "/api/system/health", + "/api/system/health/incidents" + ] + }, + { + "path": "tests/integration/specs/system-health.ui.spec.ts", + "kind": "playwright-ui", + "imports_app_modules": [], + "api_paths_exercised": [ + "**/api/system/health", + "**/api/system/health/incidents", + "**/api/system/info", + "/api/auth/verify", + "/api/system/health", + "/api/system/health/incidents" + ] + }, + { + "path": "tests/integration/specs/system.ui.spec.ts", + "kind": "playwright-ui", + "imports_app_modules": [] + }, + { + "path": "tests/integration/specs/timeline-boundary.ui.spec.ts", + "kind": "playwright-ui", + "imports_app_modules": [], + "api_paths_exercised": [ + "**/api/detection/results/**", + "**/api/recordings", + "**/api/recordings/**", + "**/api/recordings/play/*", + "**/api/timeline/segments", + "**/api/timeline/segments-by-ids" + ] + }, + { + "path": "tests/integration/specs/timeline.ui.spec.ts", + "kind": "playwright-ui", + "imports_app_modules": [] + }, + { + "path": "tests/integration/specs/users.ui.spec.ts", + "kind": "playwright-ui", + "imports_app_modules": [] + }, + { + "path": "tests/integration/synthetic-onvif-prebuffer.py", + "kind": "integration-helper-script" + }, + { + "path": "web/tests/README.md", + "kind": "doc" + }, + { + "path": "web/tests/RUNNING_TESTS_LOCALLY.md", + "kind": "doc" + }, + { + "path": "web/tests/apiTokenPolicy.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/components/preact/users/apiTokenPolicy.js" + ] + }, + { + "path": "web/tests/auditHistory.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/components/preact/users/auditHistory.js" + ] + }, + { + "path": "web/tests/authorizationPolicy.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/components/preact/users/authorizationPolicy.js" + ] + }, + { + "path": "web/tests/browserDialogs.spec.js", + "kind": "jest-unit", + "imports_app_modules": [], + "source_tree_walk_extension_filter": [ + "/\\.(?:js|jsx)$/" + ] + }, + { + "path": "web/tests/cameraFavorites.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/components/preact/live/cameraFavorites.js" + ], + "jest_mock_paths": [ + "web/js/query-client.js" + ] + }, + { + "path": "web/tests/collectionMembership.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/components/preact/fleet/collectionMembership.js" + ], + "jest_mock_paths": [ + "web/js/query-client.js" + ] + }, + { + "path": "web/tests/collectionSelector.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/components/preact/fleet/collectionSelector.js" + ] + }, + { + "path": "web/tests/detectionPolling.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/utils/detection-polling.js" + ] + }, + { + "path": "web/tests/e2e/pages/LoginPage.js", + "kind": "selenium-e2e-support", + "imports_app_modules": [] + }, + { + "path": "web/tests/e2e/pages/NavigationMenu.js", + "kind": "selenium-e2e-support", + "imports_app_modules": [] + }, + { + "path": "web/tests/e2e/pages/StreamsPage.js", + "kind": "selenium-e2e-support", + "imports_app_modules": [] + }, + { + "path": "web/tests/e2e/specs/login.spec.js", + "kind": "selenium-e2e-spec", + "imports_app_modules": [] + }, + { + "path": "web/tests/e2e/specs/navigation.spec.js", + "kind": "selenium-e2e-spec", + "imports_app_modules": [] + }, + { + "path": "web/tests/e2e/specs/streams.spec.js", + "kind": "selenium-e2e-spec", + "imports_app_modules": [] + }, + { + "path": "web/tests/e2e/utils/test-utils.js", + "kind": "selenium-e2e-support", + "imports_app_modules": [] + }, + { + "path": "web/tests/eptzConfig.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/utils/eptz-config.js" + ] + }, + { + "path": "web/tests/eventRouting.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/components/preact/events/eventRouting.js" + ] + }, + { + "path": "web/tests/fetchUtils.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/fetch-utils.js" + ] + }, + { + "path": "web/tests/fleetOrganization.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/components/preact/fleet/fleetOrganization.js" + ] + }, + { + "path": "web/tests/fleetQuery.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/components/preact/fleet/fleetQuery.js" + ] + }, + { + "path": "web/tests/forcedPasswordChange.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/components/preact/forcedPasswordChange.js" + ] + }, + { + "path": "web/tests/fullscreenFallback.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/components/preact/fullscreenApi.js" + ] + }, + { + "path": "web/tests/go2rtcSettings.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/fetch-utils.js", + "web/js/utils/settings-utils.js" + ], + "jest_mock_paths": [ + "web/js/fetch-utils.js" + ] + }, + { + "path": "web/tests/i18n.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/i18n.js" + ], + "literal_source_paths": [ + "web/js/components/preact/Footer.jsx" + ], + "reads_source_text": true + }, + { + "path": "web/tests/install-and-run.sh", + "kind": "script" + }, + { + "path": "web/tests/investigationUtils.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/components/preact/investigation/investigationUtils.js" + ] + }, + { + "path": "web/tests/layoutStyles.spec.js", + "kind": "jest-unit", + "imports_app_modules": [] + }, + { + "path": "web/tests/lazyFilterOptions.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/components/preact/recordings/lazyFilterOptions.js" + ] + }, + { + "path": "web/tests/liveDisplayMode.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/components/preact/useLiveDisplayMode.js" + ] + }, + { + "path": "web/tests/liveFullscreenPreferences.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/components/preact/useAlwaysFullscreenOnTap.js" + ] + }, + { + "path": "web/tests/liveOperator.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/components/preact/live/liveOperator.js" + ] + }, + { + "path": "web/tests/liveSlideshow.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/components/preact/useLiveSlideshow.js" + ] + }, + { + "path": "web/tests/liveSlideshowControl.spec.js", + "kind": "jest-unit", + "imports_app_modules": [], + "literal_source_paths": [ + "web/js/components/preact/LiveSlideshowControl.jsx" + ], + "esbuild_external_suffix_stubs": [ + "i18n.js" + ], + "bundles_with_esbuild": true + }, + { + "path": "web/tests/liveSlideshowHook.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/components/preact/useLiveSlideshow.js" + ] + }, + { + "path": "web/tests/liveTileStatus.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/components/preact/liveTileStatus.js" + ] + }, + { + "path": "web/tests/liveVideoErrors.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/utils/live-video-errors.js" + ] + }, + { + "path": "web/tests/liveViewTransportTabs.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/utils/live-view-url.js" + ] + }, + { + "path": "web/tests/liveViewUrl.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/utils/live-view-url.js" + ] + }, + { + "path": "web/tests/locale-coverage.spec.js", + "kind": "jest-unit", + "imports_app_modules": [], + "source_tree_walk_extension_filter": [ + "/\\.jsx?$/" + ] + }, + { + "path": "web/tests/lprSearch.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/components/preact/lpr/lprSearch.js", + "web/js/components/preact/timeline/timelineUtils.js" + ] + }, + { + "path": "web/tests/mobileLiveGestures.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/components/preact/mobileLiveGestures.js" + ] + }, + { + "path": "web/tests/navigation-utils.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/utils/navigation-utils.js" + ] + }, + { + "path": "web/tests/open-issues-regressions.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/components/preact/liveStreamPolicy.js", + "web/js/components/preact/recordings/formatUtils.js", + "web/js/components/preact/system/SystemUtils.js", + "web/js/components/preact/timeline/timelineUtils.js" + ], + "literal_source_paths": [ + "web/js/components/preact/investigation/InvestigationView.jsx" + ], + "reads_source_text": true + }, + { + "path": "web/tests/pictureInPicture.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/components/preact/pictureInPicture.js" + ] + }, + { + "path": "web/tests/planSketch.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/components/preact/live/planSketch.js" + ] + }, + { + "path": "web/tests/platesView.spec.js", + "kind": "jest-unit", + "imports_app_modules": [], + "literal_source_paths": [ + "web/js/components/preact/lpr/PlatesView.jsx" + ], + "esbuild_external_suffix_stubs": [ + "query-client.js", + "i18n.js", + "fetch-utils.js", + "stream-summaries.js", + "ModalDialog.jsx", + "ToastContainer.jsx" + ], + "bundles_with_esbuild": true + }, + { + "path": "web/tests/playbackTransport.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/utils/playback-transport.js" + ] + }, + { + "path": "web/tests/ptzActions.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/components/preact/ptzActions.js" + ] + }, + { + "path": "web/tests/recordingPlayback.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/utils/recording-playback.js" + ] + }, + { + "path": "web/tests/recordingsAPI.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/recordings/recordingsAPI.jsx", + "web/js/fetch-utils.js", + "web/js/query-client.js" + ], + "jest_mock_paths": [ + "web/js/components/preact/ToastContainer.jsx", + "web/js/fetch-utils.js", + "web/js/i18n.js", + "web/js/query-client.js" + ] + }, + { + "path": "web/tests/recordingsUrlUtils.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/components/preact/recordings/urlUtils.js" + ] + }, + { + "path": "web/tests/requestQueue.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/request-queue.js" + ] + }, + { + "path": "web/tests/setup.cjs", + "kind": "jest-setup", + "imports_app_modules": [] + }, + { + "path": "web/tests/storageTargetRouting.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/components/preact/settings/storageTargetRouting.js" + ] + }, + { + "path": "web/tests/streamAvailability.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/utils/stream-availability.js" + ], + "literal_source_paths": [ + "web/js/components/preact/StreamsView.jsx" + ], + "reads_source_text": true + }, + { + "path": "web/tests/streamSummaries.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/utils/stream-summaries.js" + ] + }, + { + "path": "web/tests/systemHealth.spec.js", + "kind": "jest-unit", + "imports_app_modules": [], + "literal_source_paths": [ + "web/js/components/preact/system/HealthIncidentHistory.jsx", + "web/js/components/preact/system/MemoryStorage.jsx", + "web/js/components/preact/system/SystemHealth.jsx" + ], + "bundles_with_esbuild": true + }, + { + "path": "web/tests/systemHealthSettings.spec.js", + "kind": "jest-unit", + "imports_app_modules": [], + "literal_source_paths": [ + "web/js/components/preact/settings/HealthTab.jsx" + ], + "bundles_with_esbuild": true + }, + { + "path": "web/tests/timeline-utils.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/components/preact/timeline/timelineUtils.js" + ] + }, + { + "path": "web/tests/useAsyncAction.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/hooks/useAsyncAction.js" + ] + }, + { + "path": "web/tests/webrtcIceGathering.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/utils/webrtc-ice-gathering.js" + ] + }, + { + "path": "web/tests/workspacePreferences.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/utils/workspace-preferences.js" + ] + }, + { + "path": "web/tests/zoneEditorGeometry.spec.js", + "kind": "jest-unit", + "imports_app_modules": [ + "web/js/utils/zone-editor-geometry.js" + ] + } + ], + "extension_sensitive_consumers": { + "tests": [ + { + "test": "tests/integration/recordings-url-utils.ui.spec.ts", + "kind": "playwright-ui", + "reasons": [ + "Playwright spec imports app source directly: web/js/components/preact/recordings/urlUtils.js" + ], + "targets": [ + "web/js/components/preact/recordings/urlUtils.js" + ] + }, + { + "test": "web/tests/browserDialogs.spec.js", + "kind": "jest-unit", + "reasons": [ + "walks web/js and filters by extension regex /\\.(?:js|jsx)$/ (TS/TSX files would be silently skipped)" + ], + "targets": [] + }, + { + "test": "web/tests/i18n.spec.js", + "kind": "jest-unit", + "reasons": [ + "literal source path(s): web/js/components/preact/Footer.jsx" + ], + "targets": [ + "web/js/components/preact/Footer.jsx", + "web/js/i18n.js" + ] + }, + { + "test": "web/tests/liveSlideshowControl.spec.js", + "kind": "jest-unit", + "reasons": [ + "literal source path(s): web/js/components/preact/LiveSlideshowControl.jsx", + "esbuild external/stub keyed by filename suffix: i18n.js" + ], + "targets": [ + "web/js/components/preact/LiveSlideshowControl.jsx" + ] + }, + { + "test": "web/tests/locale-coverage.spec.js", + "kind": "jest-unit", + "reasons": [ + "walks web/js and filters by extension regex /\\.jsx?$/ (TS/TSX files would be silently skipped)" + ], + "targets": [] + }, + { + "test": "web/tests/open-issues-regressions.spec.js", + "kind": "jest-unit", + "reasons": [ + "literal source path(s): web/js/components/preact/investigation/InvestigationView.jsx" + ], + "targets": [ + "web/js/components/preact/investigation/InvestigationView.jsx", + "web/js/components/preact/liveStreamPolicy.js", + "web/js/components/preact/recordings/formatUtils.js", + "web/js/components/preact/system/SystemUtils.js", + "web/js/components/preact/timeline/timelineUtils.js" + ] + }, + { + "test": "web/tests/platesView.spec.js", + "kind": "jest-unit", + "reasons": [ + "literal source path(s): web/js/components/preact/lpr/PlatesView.jsx", + "esbuild external/stub keyed by filename suffix: query-client.js, i18n.js, fetch-utils.js, stream-summaries.js, ModalDialog.jsx, ToastContainer.jsx" + ], + "targets": [ + "web/js/components/preact/lpr/PlatesView.jsx" + ] + }, + { + "test": "web/tests/recordingsAPI.spec.js", + "kind": "jest-unit", + "reasons": [ + "imports/mocks .jsx module path(s): web/js/components/preact/ToastContainer.jsx, web/js/components/preact/recordings/recordingsAPI.jsx, web/js/components/preact/ToastContainer.jsx" + ], + "targets": [ + "web/js/components/preact/ToastContainer.jsx", + "web/js/components/preact/recordings/recordingsAPI.jsx", + "web/js/fetch-utils.js", + "web/js/i18n.js", + "web/js/query-client.js" + ] + }, + { + "test": "web/tests/streamAvailability.spec.js", + "kind": "jest-unit", + "reasons": [ + "literal source path(s): web/js/components/preact/StreamsView.jsx" + ], + "targets": [ + "web/js/components/preact/StreamsView.jsx", + "web/js/utils/stream-availability.js" + ] + }, + { + "test": "web/tests/systemHealth.spec.js", + "kind": "jest-unit", + "reasons": [ + "literal source path(s): web/js/components/preact/system/HealthIncidentHistory.jsx, web/js/components/preact/system/MemoryStorage.jsx, web/js/components/preact/system/SystemHealth.jsx" + ], + "targets": [ + "web/js/components/preact/system/HealthIncidentHistory.jsx", + "web/js/components/preact/system/MemoryStorage.jsx", + "web/js/components/preact/system/SystemHealth.jsx" + ] + }, + { + "test": "web/tests/systemHealthSettings.spec.js", + "kind": "jest-unit", + "reasons": [ + "literal source path(s): web/js/components/preact/settings/HealthTab.jsx" + ], + "targets": [ + "web/js/components/preact/settings/HealthTab.jsx" + ] + } + ], + "config_and_tooling": [ + { + "path": "web/jest.config.cjs", + "detail": "testMatch '**/tests/**/*.spec.js' and transform '^.+\\.jsx?$' only: .ts/.tsx specs/modules are neither discovered nor transformed." + }, + { + "path": "web/jest.e2e.config.cjs", + "detail": "testMatch '**/tests/e2e/**/*.spec.js', transform '^.+\\.jsx?$'." + }, + { + "path": "web/babel.config.cjs", + "detail": "Babel config used by Jest; several specs note it has no JSX transform, hence esbuild bundling in tests." + }, + { + "path": "web/eslint.config.js", + "detail": "files globs 'js/**/*.js','js/**/*.jsx' and 'js/components/**/*.{js,jsx}' (i18n rule): TS/TSX would not be linted." + }, + { + "path": "web/eslint-rules/no-literal-ui-string.js", + "detail": "Looks up web/i18n-literal-baseline.json by path relative to web/js/components/ including extension; a .jsx->.tsx rename loses the allowance (lint fails) or needs a new key (T10)." + }, + { + "path": "web/i18n-literal-baseline.json", + "detail": "Path-keyed (e.g. \"preact/StreamConfigModal.jsx\") per-file literal allowance counts." + }, + { + "path": "web/vite.config.js", + "detail": "rollupOptions.input lists 11 HTML pages; MISSING_APP_JS_IMPORTERS=['streams.html']; aliases @js/@css/@img." + }, + { + "path": "web/*.html", + "detail": "Each HTML page references ./js/pages/.jsx by literal path in +`; + legacyB = ` +`; + } + + write(path.join(dist, 'index.html'), ` + + + +${noise(11, 900)}${legacyA}\n`); + write(path.join(dist, 'other.html'), ` + + +${legacyB}\n`); + + const manifest = { locales: locales.map((code) => ({ code, name: code })) }; + write(path.join(dist, 'locales', 'manifest.json'), JSON.stringify(manifest), { gz: false }); + for (const code of locales) { + write(path.join(dist, 'locales', `${code}.json`), JSON.stringify({ k: noise(code.length * 13, 5000) })); + write(path.join(dist, 'locales', `${code}.png`), Buffer.alloc(300, 7), { gz: false }); + } + // Source locale manifest: lists every available locale. + write(path.join(web, 'public', 'locales', 'manifest.json'), + JSON.stringify({ locales: [{ code: 'en' }, { code: 'de' }] }), { gz: false }); + + // Make sources clearly older than the build output. + const past = new Date(Date.now() - 3600_000); + for (const f of ['js/app.js', 'index.html', 'vite.config.js', 'public/locales/manifest.json']) { + fs.utimesSync(path.join(web, f), past, past); + } + return { root, web, dist, sizes: { shared, lazy, entryA, entryB, css } }; +} + +function bytes(s) { + return Buffer.byteLength(s); +} +function gz9(s) { + return zlib.gzipSync(s, { level: 9 }).length; +} +function br11(s) { + return zlib.brotliCompressSync(s, { + params: { [zlib.constants.BROTLI_PARAM_QUALITY]: 11 }, + }).length; +} +function fileSize(p) { + return fs.statSync(p).size; +} + +function profileDef(overrides = {}) { + return { + expect: { legacy: true, locales: 'all' }, + entries: ['index', 'other'], + budgets: {}, + ...overrides, + }; +} + +function writeBudgets(dir, profiles) { + const file = path.join(dir, 'web-budgets.json'); + fs.writeFileSync(file, `${JSON.stringify({ schemaVersion: 1, profiles }, null, 2)}\n`); + return file; +} + +function runCli(args) { + return spawnSync(process.execPath, [script, ...args], { encoding: 'utf8' }); +} + +test('accounts installed vs transfer bytes and never double-counts shared chunks', () => { + const fx = makeFixture(); + const report = analyzeDist(fx.dist); + const m = flattenMetrics(report); + + // Installed bytes: every file on disk exactly once, including .gz copies. + let total = 0; + let gzTotal = 0; + const walk = (d) => { + for (const e of fs.readdirSync(d, { withFileTypes: true })) { + const p = path.join(d, e.name); + if (e.isDirectory()) walk(p); + else { + total += fileSize(p); + if (e.name.endsWith('.gz')) gzTotal += fileSize(p); + } + } + }; + walk(fx.dist); + assert.equal(m['installed.totalBytes'], total); + assert.equal(m['installed.precompressedCopyBytes'], gzTotal); + assert.equal(m['installed.originalBytes'], total - gzTotal); + + // Unique modern JS: each chunk once, although shared-AAAA.js is used by two entries. + const modernJs = [fx.sizes.shared, fx.sizes.lazy, fx.sizes.entryA, fx.sizes.entryB]; + assert.equal(m['transfer.unique.modernJs.raw'], modernJs.reduce((a, s) => a + bytes(s), 0)); + assert.equal(m['transfer.unique.modernJs.gzip'], modernJs.reduce((a, s) => a + gz9(s), 0)); + assert.equal(m['transfer.unique.modernJs.brotli'], modernJs.reduce((a, s) => a + br11(s), 0)); + // "served" is what the LightNVR server sends: the precompressed .gz sibling. + const servedShared = fileSize(path.join(fx.dist, 'assets', 'shared-AAAA.js.gz')); + assert.ok(servedShared < bytes(fx.sizes.shared)); + + // Per-entry initial JS = entry + its static closure; lazy chunks are separate. + assert.equal(m['entries.index.modern.initialJs.raw'], bytes(fx.sizes.entryA) + bytes(fx.sizes.shared)); + assert.equal(m['entries.index.modern.initialJs.gzip'], gz9(fx.sizes.entryA) + gz9(fx.sizes.shared)); + assert.equal(m['entries.index.modern.lazyJs.raw'], bytes(fx.sizes.lazy)); + assert.equal(m['entries.other.modern.initialJs.raw'], bytes(fx.sizes.entryB) + bytes(fx.sizes.shared)); + assert.equal(m['entries.other.modern.lazyJs.raw'], 0); + assert.equal(m['entries.index.modern.initialCss.raw'], bytes(fx.sizes.css)); + assert.equal( + m['entries.index.modern.initialJs.served'], + fileSize(path.join(fx.dist, 'assets', 'entry-a-CCCC.js.gz')) + servedShared, + ); + + // The shared chunk is reported once, attributed to both entries. + const shared = report.chunks.shared.find((c) => c.file === 'assets/shared-AAAA.js'); + assert.ok(shared, 'shared chunk listed'); + assert.deepEqual([...shared.entries].sort(), ['index', 'other']); + assert.ok(report.chunks.lazy.some((c) => c.file === 'assets/lazy-BBBB.js')); + + // Legacy graph resolved through System.register and t.import(). + assert.ok(m['entries.index.legacy.initialJs.raw'] > 0); + assert.equal( + m['entries.index.legacy.lazyJs.raw'], + fileSize(path.join(fx.dist, 'assets', 'lazy-legacy-RRRR.js')), + ); + assert.equal(m['build.legacyChunks'] > 0, true); + + // Locales are counted as installed bytes, including their .gz copies. + const locBytes = ['en', 'de'].reduce((a, c) => { + const base = path.join(fx.dist, 'locales', `${c}.json`); + return a + fileSize(base) + fileSize(`${base}.gz`) + fileSize(path.join(fx.dist, 'locales', `${c}.png`)); + }, fileSize(path.join(fx.dist, 'locales', 'manifest.json'))); + assert.equal(m['installed.locales.totalBytes'], locBytes); + assert.equal(m['build.localeCount'], 2); +}); + +test('check passes for a fresh, in-budget fixture', () => { + const fx = makeFixture(); + const result = checkProfile({ + distDir: fx.dist, + webRoot: fx.web, + profileName: 'default', + profile: profileDef({ budgets: { 'installed.totalBytes': 10_000_000 } }), + }); + assert.deepEqual(result.failures, []); + assert.equal(result.ok, true); +}); + +test('check rejects a missing dist and a missing referenced artifact', () => { + const fx = makeFixture(); + const profile = profileDef({ budgets: { 'installed.totalBytes': 10_000_000 } }); + + fs.rmSync(path.join(fx.dist, 'assets', 'shared-AAAA.js')); + let result = checkProfile({ distDir: fx.dist, webRoot: fx.web, profileName: 'default', profile }); + assert.equal(result.ok, false); + assert.ok(result.failures.some((f) => /missing/i.test(f)), result.failures.join('\n')); + + fs.rmSync(path.join(fx.dist, 'other.html')); + result = checkProfile({ distDir: fx.dist, webRoot: fx.web, profileName: 'default', profile }); + assert.ok(result.failures.some((f) => /missing entry.*other/i.test(f)), result.failures.join('\n')); + + fs.rmSync(fx.dist, { recursive: true }); + result = checkProfile({ distDir: fx.dist, webRoot: fx.web, profileName: 'default', profile }); + assert.equal(result.ok, false); + assert.ok(result.failures.some((f) => /missing/i.test(f))); +}); + +test('check rejects a stale dist (sources newer than the build)', () => { + const fx = makeFixture(); + const future = new Date(Date.now() + 60_000); + fs.utimesSync(path.join(fx.web, 'js', 'app.js'), future, future); + const result = checkProfile({ + distDir: fx.dist, + webRoot: fx.web, + profileName: 'default', + profile: profileDef({ budgets: { 'installed.totalBytes': 10_000_000 } }), + }); + assert.equal(result.ok, false); + assert.ok(result.failures.some((f) => /stale/i.test(f) && /js\/app\.js/.test(f)), result.failures.join('\n')); +}); + +test('check rejects a dist built for a different profile', () => { + const fx = makeFixture({ legacy: true, locales: ['en', 'de'] }); + const result = checkProfile({ + distDir: fx.dist, + webRoot: fx.web, + profileName: 'modern', + profile: profileDef({ expect: { legacy: false, locales: ['en'] }, budgets: { 'installed.totalBytes': 10_000_000 } }), + }); + assert.equal(result.ok, false); + assert.ok(result.failures.some((f) => /legacy/i.test(f)), result.failures.join('\n')); + assert.ok(result.failures.some((f) => /locale/i.test(f)), result.failures.join('\n')); +}); + +test('check rejects a deliberately oversized artifact and unknown budget metrics', () => { + const fx = makeFixture(); + const baseline = flattenMetrics(analyzeDist(fx.dist)); + const profile = profileDef({ + budgets: { + 'installed.totalBytes': baseline['installed.totalBytes'] + 100, + 'entries.index.modern.initialJs.served': baseline['entries.index.modern.initialJs.served'] + 100, + }, + }); + assert.equal(checkProfile({ distDir: fx.dist, webRoot: fx.web, profileName: 'default', profile }).ok, true); + + // Grow the shared chunk by ~200 KiB of incompressible data. + const sharedPath = path.join(fx.dist, 'assets', 'shared-AAAA.js'); + const big = `${fs.readFileSync(sharedPath, 'utf8')}const pad=${JSON.stringify(crypto.randomBytes(150_000).toString('base64'))};\n`; + write(sharedPath, big); + const result = checkProfile({ distDir: fx.dist, webRoot: fx.web, profileName: 'default', profile }); + assert.equal(result.ok, false); + assert.ok(result.failures.some((f) => /installed\.totalBytes/.test(f) && /over budget/i.test(f)), result.failures.join('\n')); + assert.ok(result.failures.some((f) => /entries\.index\.modern\.initialJs\.served/.test(f)), result.failures.join('\n')); + + const unknown = checkProfile({ + distDir: fx.dist, + webRoot: fx.web, + profileName: 'default', + profile: profileDef({ budgets: { 'no.such.metric': 5 } }), + }); + assert.equal(unknown.ok, false); + assert.ok(unknown.failures.some((f) => /no\.such\.metric/.test(f))); + + const empty = checkProfile({ distDir: fx.dist, webRoot: fx.web, profileName: 'default', profile: profileDef() }); + assert.equal(empty.ok, false, 'a profile without budgets must not pass'); +}); + +test('CLI --check exits nonzero on overrun and never rewrites the budgets file', () => { + const fx = makeFixture(); + const budgetsFile = writeBudgets(fx.root, { + default: profileDef({ budgets: { 'installed.totalBytes': 1000 } }), + }); + const before = fs.readFileSync(budgetsFile); + const beforeStat = fs.statSync(budgetsFile); + + const res = runCli(['--check', '--profile', 'default', '--dist', fx.dist, '--web-root', fx.web, '--budgets', budgetsFile]); + assert.notEqual(res.status, 0, res.stdout + res.stderr); + assert.match(res.stdout + res.stderr, /over budget/i); + + const after = fs.readFileSync(budgetsFile); + assert.ok(before.equals(after), 'budgets file content changed'); + assert.equal(fs.statSync(budgetsFile).mtimeMs, beforeStat.mtimeMs, 'budgets file was rewritten'); + assert.deepEqual(fs.readdirSync(fx.root).sort(), ['web', 'web-budgets.json'], 'no sidecar files written'); + + // Unknown profile and missing budgets file are hard failures too. + assert.notEqual(runCli(['--check', '--profile', 'nope', '--dist', fx.dist, '--web-root', fx.web, '--budgets', budgetsFile]).status, 0); + assert.notEqual(runCli(['--check', '--profile', 'default', '--dist', fx.dist, '--web-root', fx.web, '--budgets', path.join(fx.root, 'absent.json')]).status, 0); +}); + +test('CLI --check succeeds for an in-budget fixture and --report prints JSON', () => { + const fx = makeFixture(); + const budgetsFile = writeBudgets(fx.root, { + default: profileDef({ budgets: { 'installed.totalBytes': 10_000_000 } }), + }); + const ok = runCli(['--check', '--profile', 'default', '--dist', fx.dist, '--web-root', fx.web, '--budgets', budgetsFile]); + assert.equal(ok.status, 0, ok.stdout + ok.stderr); + + const rep = runCli(['--report', '--dist', fx.dist]); + assert.equal(rep.status, 0, rep.stderr); + const parsed = JSON.parse(rep.stdout); + assert.equal(parsed.metrics['installed.totalBytes'] > 0, true); +}); + +test('reference parsers ignore data URIs, comments, bare CSS packages and template imports', () => { + const css = `/* see the @import line */@import "tailwindcss";@import "./theme/x.css"; +.a{background:url("data:image/svg+xml,%3Csvg filter='url(%23a)'%3E")}.b{background:url(../img/b.png)}`; + assert.deepEqual(parseCss(css).sort(), ['../img/b.png', './theme/x.css']); + + const js = 'import{a as b}from"./a-1.js";import"./b-2.js";export*from"./c-3.js";' + + 'x.import("./d-4.js");import(`./e-${n}.js`);import"data:text/javascript,1";' + + 'const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./f-5.js","../css/g.css"])))=>i.map(i=>d[i]);'; + const r = parseJs(js); + assert.deepEqual(r.statics.sort(), ['./a-1.js', './b-2.js', './c-3.js']); + assert.deepEqual(r.dynamics.sort(), ['../css/g.css', './d-4.js', './f-5.js']); +}); diff --git a/tests/quality/contracts.test.mjs b/tests/quality/contracts.test.mjs new file mode 100644 index 000000000..b886afa8e --- /dev/null +++ b/tests/quality/contracts.test.mjs @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +import { test, after } from 'node:test'; +import assert from 'node:assert/strict'; +import fs from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; +import { pathToFileURL, fileURLToPath } from 'node:url'; +import { build } from 'esbuild'; +import { spawnSync } from 'node:child_process'; +import { buildArtifacts, writeArtifacts, checkArtifacts, loadContract } from '../../scripts/quality/contracts.mjs'; + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..'); +const scratch = await fs.mkdtemp(path.join(os.tmpdir(), 'lightnvr-contracts-')); +after(() => fs.rm(scratch, { recursive: true, force: true })); +await fs.cp(path.join(root, 'docs/api'), path.join(scratch, 'docs/api'), { recursive: true }); +await fs.cp(path.join(root, 'tests/contracts/fixtures'), path.join(scratch, 'tests/contracts/fixtures'), { recursive: true }); +const artifacts = await buildArtifacts({ root: scratch }); +await writeArtifacts(scratch, artifacts); +const generated = await import(pathToFileURL(path.join(scratch, 'web/js/api/generated/validators.mjs'))); +const fixtureRoot = path.join(scratch, 'tests/contracts/fixtures/streams'); +const fixture = async name => JSON.parse(await fs.readFile(path.join(fixtureRoot, name), 'utf8')); + +test('all reviewed fixtures match their expected validation outcome', async () => { + const manifest = await fixture('index.json'); + assert.equal(manifest.fixtures.length, 91); + for (const entry of manifest.fixtures) { + const data = await fixture(entry.file); + const validator = generated['validate' + entry.schema.split('/').at(-1)]; + assert.equal(typeof validator, 'function', entry.file); + assert.equal(validator(data.body), entry.expect === 'valid', entry.file); + } +}); + +test('viewer models reject credentials without requiring sensitive fields', async () => { + const safe = (await fixture('full.live-only.sensitive-keys-absent.valid.json')).body; + assert.equal(generated.validateStreamFullResponseLiveOnly(safe), true); + safe.stream.onvif_password = 'synthetic-leak'; + assert.equal(generated.validateStreamFullResponseLiveOnly(safe), false); +}); + +test('permitted additive response fields work but client request drift fails', async () => { + const safe = (await fixture('client-config.valid.json')).body; + assert.equal(generated.validateClientConfig({ ...safe, future_addition: 1 }), true); + assert.equal(generated.validateStreamUpdateRequest({ record: true, unknown_option: 1 }), false); +}); + +test('UTF-8 byte bounds are enforced independently of Unicode character count', () => { + assert.equal(generated.validateStreamName('é'.repeat(127)), true); + assert.equal(generated.validateStreamName('é'.repeat(128)), false); + assert.equal(generated.validateStreamName('x'.repeat(255)), true); +}); + +test('JSON-in-string handles disabled, malformed and structurally wrong values', () => { + assert.equal(generated.validateEptzConfigString(''), true); + assert.equal(generated.validateEptzConfigString('{'), false); + assert.equal(generated.validateEptzConfigString('null'), false); + assert.equal(generated.validateEptzConfigString('{}'), false); +}); + +test('fresh generation is deterministic and check leaves checkout bytes untouched', async () => { + assert.deepEqual(await buildArtifacts({ root: scratch }), artifacts); + assert.deepEqual(await checkArtifacts(scratch, artifacts), []); + for (const [name, text] of Object.entries(artifacts)) assert.equal(await fs.readFile(path.join(scratch,name),'utf8'),text); +}); + +test('check rejects stale, missing and unexpected files without repairing them', async () => { + const filename='web/js/api/generated/types.ts'; + const target=path.join(scratch,filename); + await fs.writeFile(target, 'stale\n'); + assert.ok((await checkArtifacts(scratch, artifacts)).some(x => x.includes(filename))); + assert.equal(await fs.readFile(target,'utf8'),'stale\n'); + await fs.unlink(target); + assert.ok((await checkArtifacts(scratch,artifacts)).some(x => x.includes(filename))); + await fs.writeFile(target, artifacts[filename]); + const extra=path.join(scratch,'web/js/api/generated/forgotten.ts'); + await fs.writeFile(extra,'untracked'); + assert.ok((await checkArtifacts(scratch,artifacts)).some(x => x.includes('forgotten.ts'))); + await fs.unlink(extra); +}); + +test('operation manifest retains status/header and role/surface selection information', async () => { + const manifest=JSON.parse(artifacts['tests/contracts/generated/manifest.json']); + assert.deepEqual(manifest.operations.updateStream.success_statuses,[200,202]); + assert.ok(manifest.operations.getStreamFull.responses['200'].headers['Cache-Control']); + assert.equal(manifest.operations.getStreamFull.variant_by_field.field,'stream.can_control_privacy'); + assert.equal(manifest.operations.listStreams.variant_by_query.parameter,'surface'); +}); + +test('generated code has no compiler imports and can bundle for the browser', async () => { + const runtime=artifacts['web/js/api/generated/validators.mjs']; + assert.doesNotMatch(runtime, /(?1000); + for (const [name,source] of Object.entries(artifacts)) if(name.endsWith('.mjs')) assert.doesNotMatch(source,/x-lightnvr-source|src\/web\//); +}); + +test('one imported boundary does not retain all generated validators', async () => { + async function size(specifier, expression) { + const result=await build({stdin:{contents:`import ${specifier} from './web/js/api/generated/validators.mjs'; globalThis.boundary=${expression};`,resolveDir:scratch},bundle:true,write:false,format:'esm',platform:'browser',minify:true}); + return result.outputFiles[0].contents.length; + } + const one=await size('{validateClientConfig}','validateClientConfig'); + const all=await size('* as validators','validators'); + assert.ok(one < all / 2, `single boundary ${one} vs all ${all}`); +}); + +test('unresolved, remote and traversal refs fail before generation', async () => { + const doc=path.join(scratch,'docs/api/contracts/common.yaml'); + const original=await fs.readFile(doc,'utf8'); + for(const ref of ['#/components/schemas/DoesNotExist','https://example.invalid/schema.json','../../../package.json#/version']){ + await fs.writeFile(doc,original+`\n x-invalid:\n $ref: '${ref}'\n`); + await assert.rejects(loadContract(scratch)); + } + await fs.writeFile(doc,original); +}); + +test('git-untracked expected generated files cannot pass check', async () => { + assert.equal(spawnSync('git',['init','-q'],{cwd:scratch}).status,0); + const errors=await checkArtifacts(scratch,artifacts); + assert.ok(errors.some(x=>x.includes('untracked'))); + assert.equal(spawnSync('git',['add','web/js/api/generated','tests/contracts/generated'],{cwd:scratch}).status,0); + assert.deepEqual(await checkArtifacts(scratch,artifacts),[]); +}); diff --git a/tests/quality/fixtures/findings/base/inventory.json b/tests/quality/fixtures/findings/base/inventory.json new file mode 100644 index 000000000..a92aef1fb --- /dev/null +++ b/tests/quality/fixtures/findings/base/inventory.json @@ -0,0 +1,209 @@ +{ + "schema_version": 1, + "producer": "fixture", + "findings": [ + { + "id": "CODEQL-1", + "fingerprint": "codeql:cpp/sql-injection:src/database/a.c:aaaa1111bbbb2222:1", + "scanner_id": "codeql-cpp", + "rule_id": "cpp/sql-injection", + "category": "application", + "location": { + "path": "src/database/a.c", + "line": 10 + }, + "disposition": "needs-triage", + "exception_expiry": null + }, + { + "id": "CODEQL-2", + "fingerprint": "codeql:cpp/path-injection:src/core/b.c:cccc3333dddd4444:1", + "scanner_id": "codeql-cpp", + "rule_id": "cpp/path-injection", + "category": "application", + "location": { + "path": "src/core/b.c", + "line": 20 + }, + "disposition": "exception-dismissed-false-positive", + "exception_expiry": "2027-01-31", + "owner": "fixture-maintainer", + "reason": "Synthetic test-only reviewed exception", + "approval": { + "status": "approved", + "approved_by": "fixture-reviewer", + "approved_on": "2026-10-31", + "evidence": "synthetic:test-review" + } + }, + { + "id": "CLANGTIDY-3971853557f6166d", + "fingerprint": "clang-tidy:bugprone-narrowing-conversions:src/core/b.c:3971853557f6166d", + "scanner_id": "clang-tidy", + "rule_id": "bugprone-narrowing-conversions", + "category": "application", + "location": { + "path": "src/core/b.c", + "line": 5 + }, + "disposition": "baseline-debt", + "exception_expiry": "2027-04-30", + "owner": "fixture-maintainer", + "reason": "Synthetic test-only reviewed exception", + "approval": { + "status": "approved", + "approved_by": "fixture-reviewer", + "approved_on": "2026-10-31", + "evidence": "synthetic:test-review" + } + }, + { + "id": "CLANGTIDY-23b6d28e219f20e2", + "fingerprint": "clang-tidy:bugprone-narrowing-conversions:src/core/b.c:23b6d28e219f20e2", + "scanner_id": "clang-tidy", + "rule_id": "bugprone-narrowing-conversions", + "category": "application", + "location": { + "path": "src/core/b.c", + "line": 9 + }, + "disposition": "baseline-debt", + "exception_expiry": "2027-04-30", + "owner": "fixture-maintainer", + "reason": "Synthetic test-only reviewed exception", + "approval": { + "status": "approved", + "approved_by": "fixture-reviewer", + "approved_on": "2026-10-31", + "evidence": "synthetic:test-review" + } + }, + { + "id": "CLANGTIDY-6097ffe41888cb7e", + "fingerprint": "clang-tidy:readability-function-cognitive-complexity:src/core/c.c:6097ffe41888cb7e", + "scanner_id": "clang-tidy", + "rule_id": "readability-function-cognitive-complexity", + "category": "application", + "location": { + "path": "src/core/c.c", + "line": 7 + }, + "disposition": "baseline-debt", + "exception_expiry": "2027-04-30", + "owner": "fixture-maintainer", + "reason": "Synthetic test-only reviewed exception", + "approval": { + "status": "approved", + "approved_by": "fixture-reviewer", + "approved_on": "2026-10-31", + "evidence": "synthetic:test-review" + } + }, + { + "id": "CPPCHECK-032b81a083372145", + "fingerprint": "cppcheck:arrayIndexOutOfBounds:src/core/c.c:032b81a083372145", + "scanner_id": "cppcheck", + "rule_id": "arrayIndexOutOfBounds", + "category": "application", + "location": { + "path": "src/core/c.c", + "line": 12 + }, + "disposition": "needs-triage", + "exception_expiry": null + }, + { + "id": "NPM-WEB-GHSA-aaaa-bbbb-cccc-minimatch", + "fingerprint": "npm-audit:web/package-lock.json:minimatch:GHSA-aaaa-bbbb-cccc", + "scanner_id": "npm-audit-web", + "rule_id": "GHSA-aaaa-bbbb-cccc", + "category": "build-only", + "location": { + "path": "web/package-lock.json", + "package": "minimatch" + }, + "disposition": "needs-triage", + "exception_expiry": null + }, + { + "id": "DEPENDABOT-7", + "fingerprint": "dependabot:web/package-lock.json:deepmerge:GHSA-dddd-eeee-ffff", + "scanner_id": "dependabot", + "rule_id": "GHSA-dddd-eeee-ffff", + "category": "build-only", + "location": { + "path": "web/package-lock.json", + "package": "deepmerge" + }, + "disposition": "needs-triage", + "exception_expiry": null + } + ], + "buckets": [ + { + "id": "CPPCHECK-BUCKET-1", + "fingerprint": "cppcheck-bucket:constParameterPointer:src/core/b.c", + "scanner_id": "cppcheck", + "rule_id": "constParameterPointer", + "path": "src/core/b.c", + "count": 2, + "disposition": "baseline-debt", + "exception_expiry": "2027-04-30", + "owner": "fixture-maintainer", + "reason": "Synthetic test-only reviewed exception", + "approval": { + "status": "approved", + "approved_by": "fixture-reviewer", + "approved_on": "2026-10-31", + "evidence": "synthetic:test-review" + } + }, + { + "id": "CPPCHECK-BUCKET-2", + "fingerprint": "cppcheck-bucket:checkersReport:", + "scanner_id": "cppcheck", + "rule_id": "checkersReport", + "path": "", + "count": 1, + "disposition": "informational", + "exception_expiry": null + }, + { + "id": "ESLINT-BUCKET-1", + "fingerprint": "eslint-bucket:jsx-a11y/click-events-have-key-events:web/js/x.jsx", + "scanner_id": "eslint-web", + "rule_id": "jsx-a11y/click-events-have-key-events", + "path": "web/js/x.jsx", + "count": 2, + "disposition": "baseline-debt", + "exception_expiry": "2027-04-30", + "owner": "fixture-maintainer", + "reason": "Synthetic test-only reviewed exception", + "approval": { + "status": "approved", + "approved_by": "fixture-reviewer", + "approved_on": "2026-10-31", + "evidence": "synthetic:test-review" + } + }, + { + "id": "ESLINT-BUCKET-2", + "fingerprint": "eslint-bucket:jsx-a11y/no-autofocus:web/js/x.jsx", + "scanner_id": "eslint-web", + "rule_id": "jsx-a11y/no-autofocus", + "path": "web/js/x.jsx", + "count": 1, + "disposition": "baseline-debt", + "exception_expiry": "2027-04-30", + "owner": "fixture-maintainer", + "reason": "Synthetic test-only reviewed exception", + "approval": { + "status": "approved", + "approved_by": "fixture-reviewer", + "approved_on": "2026-10-31", + "evidence": "synthetic:test-review" + } + } + ], + "coverage_gaps": [] +} diff --git a/tests/quality/fixtures/findings/base/policy.json b/tests/quality/fixtures/findings/base/policy.json new file mode 100644 index 000000000..fd8b73afe --- /dev/null +++ b/tests/quality/fixtures/findings/base/policy.json @@ -0,0 +1,35 @@ +{ + "schema_version": 1, + "dispositions": { + "needs-triage": { + "exception": false, + "release_blocking": true + }, + "baseline-debt": { + "exception": true, + "release_blocking": false + }, + "exception-dismissed-false-positive": { + "exception": true, + "release_blocking": false + }, + "informational": { + "exception": false, + "release_blocking": false + } + }, + "accepted_exit_codes": { + "default": [ + 0 + ], + "npm-audit-web": [ + 0, + 1 + ] + }, + "require_run_manifest": false, + "scope_minimums": {}, + "path_renames": [], + "report_pins": [], + "accepted_gaps": [] +} diff --git a/tests/quality/fixtures/findings/base/reports/actions/container-scan-200/trivy-results.sarif b/tests/quality/fixtures/findings/base/reports/actions/container-scan-200/trivy-results.sarif new file mode 100644 index 000000000..a3c831608 --- /dev/null +++ b/tests/quality/fixtures/findings/base/reports/actions/container-scan-200/trivy-results.sarif @@ -0,0 +1,22 @@ +{ + "version": "2.1.0", + "runs": [ + { + "tool": { + "driver": { + "name": "Trivy", + "version": "0.76.1", + "rules": [] + } + }, + "results": [], + "properties": { + "imageID": "sha256:feedface", + "imageName": "lightnvr:scan", + "repoTags": [ + "lightnvr:scan" + ] + } + } + ] +} diff --git a/tests/quality/fixtures/findings/base/reports/actions/static-analysis-100/clang-tidy-report/clang-tidy-report.txt b/tests/quality/fixtures/findings/base/reports/actions/static-analysis-100/clang-tidy-report/clang-tidy-report.txt new file mode 100644 index 000000000..5d4836fab --- /dev/null +++ b/tests/quality/fixtures/findings/base/reports/actions/static-analysis-100/clang-tidy-report/clang-tidy-report.txt @@ -0,0 +1,8 @@ +[1/2] Processing file /__w/lightNVR/lightNVR/src/core/b.c. +2 warnings generated. +/__w/lightNVR/lightNVR/src/core/b.c:5:3: warning: narrowing conversion from 'long' to 'int' [bugprone-narrowing-conversions] + 5 | int x = l; +/__w/lightNVR/lightNVR/src/core/b.c:9:3: warning: narrowing conversion from 'long' to 'int' [bugprone-narrowing-conversions] +[2/2] Processing file /__w/lightNVR/lightNVR/src/core/c.c. +1 warning generated. +/__w/lightNVR/lightNVR/src/core/c.c:7:1: warning: function 'x' has cognitive complexity of 30 (threshold 25) [readability-function-cognitive-complexity] diff --git a/tests/quality/fixtures/findings/base/reports/actions/static-analysis-100/cppcheck-reports/cppcheck-report.xml b/tests/quality/fixtures/findings/base/reports/actions/static-analysis-100/cppcheck-reports/cppcheck-report.xml new file mode 100644 index 000000000..e040d3846 --- /dev/null +++ b/tests/quality/fixtures/findings/base/reports/actions/static-analysis-100/cppcheck-reports/cppcheck-report.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/tests/quality/fixtures/findings/base/reports/actions/static-analysis-100/eslint-web.json b/tests/quality/fixtures/findings/base/reports/actions/static-analysis-100/eslint-web.json new file mode 100644 index 000000000..49bb5241d --- /dev/null +++ b/tests/quality/fixtures/findings/base/reports/actions/static-analysis-100/eslint-web.json @@ -0,0 +1,36 @@ +[ + { + "filePath": "/home/runner/work/lightNVR/lightNVR/web/js/x.jsx", + "messages": [ + { + "ruleId": "jsx-a11y/click-events-have-key-events", + "severity": 1, + "message": "m", + "line": 3, + "column": 1 + }, + { + "ruleId": "jsx-a11y/click-events-have-key-events", + "severity": 1, + "message": "m", + "line": 9, + "column": 1 + }, + { + "ruleId": "jsx-a11y/no-autofocus", + "severity": 1, + "message": "m", + "line": 12, + "column": 1 + } + ], + "errorCount": 0, + "warningCount": 3 + }, + { + "filePath": "/home/runner/work/lightNVR/lightNVR/web/js/y.js", + "messages": [], + "errorCount": 0, + "warningCount": 0 + } +] diff --git a/tests/quality/fixtures/findings/base/reports/github/codeql-abcdef12.sarif b/tests/quality/fixtures/findings/base/reports/github/codeql-abcdef12.sarif new file mode 100644 index 000000000..74194dc02 --- /dev/null +++ b/tests/quality/fixtures/findings/base/reports/github/codeql-abcdef12.sarif @@ -0,0 +1,83 @@ +{ + "version": "2.1.0", + "runs": [ + { + "tool": { + "driver": { + "name": "CodeQL", + "semanticVersion": "2.27.3" + }, + "extensions": [ + { + "name": "codeql/cpp-queries", + "semanticVersion": "1.9.2" + } + ] + }, + "automationDetails": { + "id": "/language:c-cpp/" + }, + "versionControlProvenance": [ + { + "revisionId": "abcdef1234567890" + } + ], + "artifacts": [ + { + "location": { + "uri": "src/database/a.c" + } + }, + { + "location": { + "uri": "src/core/b.c" + } + } + ], + "results": [ + { + "ruleId": "cpp/sql-injection", + "message": { + "text": "query built from user input" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/database/a.c" + }, + "region": { + "startLine": 10 + } + } + } + ], + "partialFingerprints": { + "primaryLocationLineHash": "aaaa1111bbbb2222:1" + } + }, + { + "ruleId": "cpp/path-injection", + "message": { + "text": "path from user input" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/core/b.c" + }, + "region": { + "startLine": 20 + } + } + } + ], + "partialFingerprints": { + "primaryLocationLineHash": "cccc3333dddd4444:1" + } + } + ] + } + ] +} diff --git a/tests/quality/fixtures/findings/base/reports/github/dependabot-open.json b/tests/quality/fixtures/findings/base/reports/github/dependabot-open.json new file mode 100644 index 000000000..4161af604 --- /dev/null +++ b/tests/quality/fixtures/findings/base/reports/github/dependabot-open.json @@ -0,0 +1,24 @@ +[ + { + "number": 7, + "state": "open", + "created_at": "2026-10-29T00:00:00Z", + "dependency": { + "manifest_path": "web/package-lock.json", + "package": { + "ecosystem": "npm", + "name": "deepmerge" + }, + "scope": "development", + "relationship": "transitive" + }, + "security_advisory": { + "ghsa_id": "GHSA-dddd-eeee-ffff", + "severity": "medium" + }, + "security_vulnerability": { + "vulnerable_version_range": "<= 4.3.1", + "first_patched_version": null + } + } +] diff --git a/tests/quality/fixtures/findings/base/reports/npm/web-audit-all.json b/tests/quality/fixtures/findings/base/reports/npm/web-audit-all.json new file mode 100644 index 000000000..a09484187 --- /dev/null +++ b/tests/quality/fixtures/findings/base/reports/npm/web-audit-all.json @@ -0,0 +1,61 @@ +{ + "auditReportVersion": 2, + "vulnerabilities": { + "minimatch": { + "name": "minimatch", + "severity": "moderate", + "isDirect": false, + "via": [ + { + "source": 1, + "name": "minimatch", + "dependency": "minimatch", + "title": "ReDoS", + "url": "https://github.com/advisories/GHSA-aaaa-bbbb-cccc", + "severity": "moderate", + "range": "<3.1.3" + } + ], + "effects": [ + "glob" + ], + "range": "<3.1.3", + "nodes": [ + "node_modules/minimatch" + ], + "fixAvailable": true + }, + "glob": { + "name": "glob", + "severity": "moderate", + "isDirect": false, + "via": [ + "minimatch" + ], + "effects": [], + "range": "*", + "nodes": [ + "node_modules/glob" + ], + "fixAvailable": true + } + }, + "metadata": { + "vulnerabilities": { + "info": 0, + "low": 0, + "moderate": 2, + "high": 0, + "critical": 0, + "total": 2 + }, + "dependencies": { + "prod": 10, + "dev": 90, + "optional": 0, + "peer": 0, + "peerOptional": 0, + "total": 100 + } + } +} diff --git a/tests/quality/fixtures/findings/base/reports/npm/web-audit-prod.json b/tests/quality/fixtures/findings/base/reports/npm/web-audit-prod.json new file mode 100644 index 000000000..52350c73c --- /dev/null +++ b/tests/quality/fixtures/findings/base/reports/npm/web-audit-prod.json @@ -0,0 +1,22 @@ +{ + "auditReportVersion": 2, + "vulnerabilities": {}, + "metadata": { + "vulnerabilities": { + "info": 0, + "low": 0, + "moderate": 0, + "high": 0, + "critical": 0, + "total": 0 + }, + "dependencies": { + "prod": 10, + "dev": 90, + "optional": 0, + "peer": 0, + "peerOptional": 0, + "total": 100 + } + } +} diff --git a/tests/quality/fixtures/findings/base/scanners.json b/tests/quality/fixtures/findings/base/scanners.json new file mode 100644 index 000000000..e900da256 --- /dev/null +++ b/tests/quality/fixtures/findings/base/scanners.json @@ -0,0 +1,71 @@ +{ + "schema_version": 1, + "scanners": [ + { + "id": "codeql-cpp", + "report_format": "sarif-2.1.0", + "access_status": "accessible", + "report_path_convention": "build/eng-artifacts//github/codeql-.sarif", + "fingerprint_scheme": "codeql", + "tool": "CodeQL", + "tool_version": "2.27.3", + "scope": { + "category": "/language:c-cpp" + } + }, + { + "id": "clang-tidy", + "report_format": "clang-diagnostic-text (path:line:col: severity: message [check])", + "access_status": "accessible", + "report_path_convention": "build/eng-artifacts//actions/static-analysis-/clang-tidy-report/clang-tidy-report.txt", + "fingerprint_scheme": "line-tool", + "tool": "clang-tidy", + "tool_version": "Debian LLVM 21.1.8" + }, + { + "id": "cppcheck", + "report_format": "cppcheck-xml-v2 (+ text template)", + "access_status": "accessible", + "report_path_convention": "build/eng-artifacts//actions/static-analysis-/cppcheck-reports/cppcheck-report.xml", + "fingerprint_scheme": "line-tool / bucket", + "tool": "cppcheck", + "tool_version": "2.13.0" + }, + { + "id": "eslint-web", + "report_format": "eslint-json", + "access_status": "accessible", + "report_path_convention": "build/eng-artifacts//actions/static-analysis-/eslint-web.json", + "fingerprint_scheme": "bucket", + "tool": "eslint" + }, + { + "id": "trivy-container", + "report_format": "sarif-2.1.0", + "access_status": "accessible", + "report_path_convention": "build/eng-artifacts//actions/container-scan-/trivy-results.sarif", + "fingerprint_scheme": "sca", + "tool": "Trivy", + "tool_version": "0.76.1", + "scope": { + "image": "lightnvr:scan" + } + }, + { + "id": "npm-audit-web", + "report_format": "npm-audit-json (auditReportVersion 2)", + "access_status": "accessible", + "report_path_convention": "build/eng-artifacts//npm/web-audit-{all,prod}.json", + "fingerprint_scheme": "sca", + "tool": "npm audit" + }, + { + "id": "dependabot", + "report_format": "json (REST repos/{repo}/dependabot/alerts)", + "access_status": "accessible", + "report_path_convention": "build/eng-artifacts//github/dependabot-open.json", + "fingerprint_scheme": "sca", + "tool": "GitHub Dependabot alerts" + } + ] +} diff --git a/tests/quality/fixtures/findings/eslint-log/web-jsx-a11y.log b/tests/quality/fixtures/findings/eslint-log/web-jsx-a11y.log new file mode 100644 index 000000000..299c5aa2e --- /dev/null +++ b/tests/quality/fixtures/findings/eslint-log/web-jsx-a11y.log @@ -0,0 +1,11 @@ +Web JSX accessibility lint Report JSX accessibility findings 2026-10-31T06:25:45.0425138Z ##[group]Run npm run lint --prefix web +Web JSX accessibility lint Report JSX accessibility findings 2026-10-31T06:25:45.1155371Z > lightnvr-web@1.0.0 lint +Web JSX accessibility lint Report JSX accessibility findings 2026-10-31T06:25:45.1155683Z > eslint js +Web JSX accessibility lint Report JSX accessibility findings 2026-10-31T06:25:46.1000000Z +Web JSX accessibility lint Report JSX accessibility findings 2026-10-31T06:25:46.1000001Z /home/runner/work/lightNVR/lightNVR/web/js/x.jsx +Web JSX accessibility lint Report JSX accessibility findings 2026-10-31T06:25:46.1000002Z 3:1 warning Visible, non-interactive elements with click handlers must have at least one keyboard listener jsx-a11y/click-events-have-key-events +Web JSX accessibility lint Report JSX accessibility findings 2026-10-31T06:25:46.1000003Z 9:1 warning Visible, non-interactive elements with click handlers must have at least one keyboard listener jsx-a11y/click-events-have-key-events +Web JSX accessibility lint Report JSX accessibility findings 2026-10-31T06:25:46.1000004Z 12:1 warning The autoFocus prop should not be used, as it can reduce usability and accessibility for users jsx-a11y/no-autofocus +Web JSX accessibility lint Report JSX accessibility findings 2026-10-31T06:25:46.1000005Z +Web JSX accessibility lint Report JSX accessibility findings 2026-10-31T06:25:46.8727233Z ✖ 3 problems (0 errors, 3 warnings) +Web JSX accessibility lint Report JSX accessibility findings 2026-10-31T06:25:46.8727234Z diff --git a/tests/quality/foundation-workflow.test.mjs b/tests/quality/foundation-workflow.test.mjs new file mode 100644 index 000000000..8ef732fb5 --- /dev/null +++ b/tests/quality/foundation-workflow.test.mjs @@ -0,0 +1,122 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import YAML from 'yaml'; +import os from 'node:os'; +import path from 'node:path'; +import { spawnSync } from 'node:child_process'; + +const workflowPath = '.github/workflows/engineering-foundation.yml'; +const workflow = () => YAML.parse(fs.readFileSync(workflowPath, 'utf8')); +const settings = () => JSON.parse(fs.readFileSync('quality/required-checks.json', 'utf8')); +const commands = job => job.steps.map(step => step.run || '').join('\n'); + +test('foundation jobs run on every main PR without path or success bypasses', () => { + const w = workflow(); + assert.deepEqual(w.on.pull_request.branches, ['main']); + for (const event of [w.on.pull_request, w.on.push]) { + assert.equal(event.paths, undefined); assert.equal(event['paths-ignore'], undefined); + } + for (const job of Object.values(w.jobs)) { + assert.equal(job.if, undefined); assert.equal(job['continue-on-error'], undefined); + assert.ok(job['timeout-minutes'] > 0); + for (const step of job.steps) { + assert.equal(step['continue-on-error'], undefined); + if (step.run) assert.equal(step.if, undefined); + } + assert.doesNotMatch(commands(job), /\|\|\s*(true|:)|--update-baseline|--generate|--capture|--propose/); + } +}); + +test('required identities map to actual jobs and remain proposals', () => { + const w = workflow(), s = settings(); + assert.equal(s.status, 'proposed-not-applied'); + assert.equal(s.remote_changes_applied, false); + assert.equal(s.future_contract_lane.status, 'pending-T39'); + assert.equal(s.future_contract_lane.required_check, null); + const ours = s.checks.filter(check => check.workflow === workflowPath); + assert.equal(ours.length, Object.keys(w.jobs).length); + for (const check of ours) { + assert.ok(w.jobs[check.job_id]); + assert.equal(check.context, w.jobs[check.job_id].name); + assert.equal(check.enforcement_verified, false); + } + assert.equal(new Set(s.checks.map(check => check.context)).size, s.checks.length); +}); + +test('type, lint, unit and schema jobs execute real existing commands', () => { + const jobs = workflow().jobs; + assert.match(commands(jobs.types), /npm --prefix web run typecheck/); + assert.match(commands(jobs.types), /npm run typecheck:tests/); + assert.match(commands(jobs.lint), /npm --prefix web run lint/); + assert.match(commands(jobs.unit), /npm --prefix web test -- --runInBand/); + assert.match(commands(jobs.schema), /node scripts\/quality\/contracts.mjs --check/); + assert.doesNotMatch(commands(jobs.unit), /passWithNoTests/); + assert.match(commands(jobs.tooling), /node --test tests\/quality\/\*\.test\.mjs/); + assert.match(commands(jobs.tooling), /python3 -m unittest discover -s tests\/quality/); +}); + +test('default and modern asset jobs build the matching profile before checking', () => { + const jobs = workflow().jobs; + for (const profile of ['default', 'modern']) { + const job = jobs['assets-' + profile], c = commands(job); + assert.match(c, /npm --prefix web run build/); + assert.match(c, new RegExp('assets.mjs --check --profile ' + profile)); + assert.ok(c.indexOf('npm --prefix web run build') < c.indexOf('assets.mjs --check')); + assert.equal(String(job.env.LIGHTNVR_WEB_LEGACY), profile === 'modern' ? 'false' : 'true'); + assert.ok(job.steps.some(step => step.uses?.startsWith('actions/upload-artifact@') && step.if === 'always()')); + } +}); + +test('every job uses locked Node and installs both existing dependency trees', () => { + for (const job of Object.values(workflow().jobs)) { + assert.ok(job.steps.some(step => step.uses?.startsWith('actions/setup-node@') && step.with?.['node-version-file'] === '.nvmrc')); + assert.match(commands(job), /npm ci\s*\n/); + assert.match(commands(job), /npm ci --prefix web/); + assert.equal(job.defaults.run.shell, 'bash'); + } +}); + +test('analysis proposal points to the full coverage job, never an individual lane', () => { + const s = settings(); + const analysis = s.checks.filter(check => check.workflow === '.github/workflows/static-analysis.yml'); + assert.equal(analysis.length, 1); + const w = YAML.parse(fs.readFileSync('.github/workflows/static-analysis.yml', 'utf8')); + const job = w.jobs[analysis[0].job_id]; + assert.ok(job); + assert.equal(analysis[0].context, job.name); + assert.match(commands(job), /scripts\/quality\/findings.py --check/); + assert.doesNotMatch(commands(job), /findings_lane.py/); +}); + + +test('a failing check exits nonzero through diagnostic tee and retains its log', () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'foundation-failure-')); + try { + const bin = path.join(dir, 'bin'); fs.mkdirSync(bin); + for (const tool of ['npm', 'node', 'python3']) { + fs.writeFileSync(path.join(bin, tool), '#!/bin/sh\necho injected-check-failure\nexit 7\n', { mode: 0o755 }); + } + for (const [id, job] of Object.entries(workflow().jobs)) { + const step = job.steps.find(step => step.name === 'Run checks'); + const result = spawnSync('bash', ['-eo', 'pipefail', '-c', step.run], { cwd: dir, env: { ...process.env, PATH: bin + ':' + process.env.PATH }, encoding: 'utf8' }); + assert.equal(result.status, 7, id + ': ' + result.stdout + result.stderr); + assert.match(fs.readFileSync(path.join(dir, 'build/eng-artifacts/foundation-' + id, 'check.log'), 'utf8'), /injected-check-failure/); + } + } finally { fs.rmSync(dir, { recursive: true, force: true }); } +}); + + +test('tooling preserves durable migration guards and checks generated schema without rewriting', () => { + const c = commands(workflow().jobs.tooling); + assert.ok(c.includes('python3 tests/operational/lifecycle/test_stale_guard_migration.py')); + assert.ok(c.includes('python3 tests/unit/test_embedded_migration_generator.py')); + assert.ok(c.includes('python3 scripts/generate_embedded_migrations.py --check db/migrations include/database/db_embedded_migrations.h')); + assert.doesNotMatch(c, /--update/); +}); + +test('tooling verifies soak qualification failure propagation without running hardware scenarios', () => { + const c = commands(workflow().jobs.tooling); + assert.ok(c.includes('python3 tests/operational/soak/test_soak.py')); + assert.doesNotMatch(c, /scripts\/quality\/soak.py --profile/); +}); diff --git a/tests/quality/lint-compat.test.mjs b/tests/quality/lint-compat.test.mjs new file mode 100644 index 000000000..f4c33ebce --- /dev/null +++ b/tests/quality/lint-compat.test.mjs @@ -0,0 +1,128 @@ +import assert from 'node:assert/strict'; +import { mkdtemp, mkdir, writeFile, rm } from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; +import { createRequire } from 'node:module'; +import { fileURLToPath } from 'node:url'; +import test from 'node:test'; +import config from '../../web/eslint.config.js'; + +const web = fileURLToPath(new URL('../../web/', import.meta.url)); +const require = createRequire(new URL('../../web/package.json', import.meta.url)); +const { ESLint } = require('eslint'); +const lint = new ESLint({ cwd: web, overrideConfigFile: true, overrideConfig: config }); +const literalRule = 'lightnvr-i18n/no-literal-ui-string'; +const duplicateRule = 'lightnvr-source/no-duplicate-implementation'; +async function messages(source, filename = 'js/components/preact/T10Probe.tsx') { + return (await lint.lintText(source, { filePath: path.join(web, filename) }))[0].messages; +} +function forRule(result, rule) { return result.filter((message) => message.ruleId === rule); } + +for (const extension of ['jsx', 'tsx']) { + test(`${extension} retains the Footer allowance exactly once`, async () => { + const filename = `js/components/preact/Footer.${extension}`; + assert.equal(forRule(await messages('export const Footer = () =>
LightNVR ©
;', filename), literalRule).length, 0); + const result = await messages('export const Footer = () =>
LightNVR ©LightNVR ©
;', filename); + assert.equal(forRule(result, literalRule).length, 1); + }); +} + +for (const expression of [ + "('Untranslated greeting' as string)", + "('Untranslated greeting' satisfies string)", + "('Untranslated greeting' as string)!", + "(true ? ('Untranslated greeting' as string) : t('key'))", + "`${('Untranslated greeting' as string)}`", + "[('Untranslated greeting' as string)]", +]) { + test(`typed JSX child is checked: ${expression}`, async () => { + const result = await messages(`export const View = () =>
{${expression}}
;`); + assert.equal(forRule(result, literalRule).length, 1, JSON.stringify(result)); + }); +} + +for (const attribute of ['title', 'aria-label', 'placeholder', 'alt']) { + test(`typed visible attribute ${attribute} is checked`, async () => { + const result = await messages(`export const View = () => ;`); + assert.equal(forRule(result, literalRule).length, 1); + }); +} + +test('translated calls and non-visible strings remain allowed', async () => { + const result = await messages("export const View = () =>
{t('message')}
;"); + assert.equal(forRule(result, literalRule).length, 0); +}); + +test('nested wrapper occurrences consume the same canonical allowance', async () => { + const result = await messages("export const Footer = () =>
{('LightNVR ©' as string)}{('LightNVR ©' satisfies string)}
;", 'js/components/preact/Footer.tsx'); + assert.equal(forRule(result, literalRule).length, 1); +}); + +test('TSX hook order and accessibility violations are blocking', async () => { + const result = await messages("import {useState} from 'preact/hooks'; export function View({enabled}: {enabled: boolean}) { if(enabled) useState(0); return ; }"); + assert.equal(forRule(result, 'react-hooks/rules-of-hooks')[0]?.severity, 2, JSON.stringify(result)); + assert.equal(forRule(result, 'jsx-a11y/alt-text')[0]?.severity, 2); +}); + +test('TSX missing effect dependencies are blocking', async () => { + const result = await messages("import {useEffect} from 'preact/hooks'; export function View({value}: {value: string}) { useEffect(() => { console.log(value); }, []); return null; }"); + assert.equal(forRule(result, 'react-hooks/exhaustive-deps')[0]?.severity, 2, JSON.stringify(result)); +}); + +test('legacy accessibility remains visible at warning severity', async () => { + const result = await messages("export const View = () => ;", 'js/components/preact/T10Probe.jsx'); + assert.equal(forRule(result, 'jsx-a11y/alt-text')[0]?.severity, 1); +}); + +test('explicit any is blocked in migrated source', async () => { + const result = await messages('export const decode = (value: any) => value;', 'js/api/T10Probe.ts'); + assert.equal(forRule(result, '@typescript-eslint/no-explicit-any')[0]?.severity, 2); +}); + +for (const directive of ['ts-nocheck', 'ts-ignore', 'ts-expect-error']) { + test(`@${directive} cannot bypass migrated checks`, async () => { + const result = await messages(`// @${directive}\nexport const number: number = 'invalid';`, 'js/api/T10Probe.ts'); + assert.equal(forRule(result, '@typescript-eslint/ban-ts-comment')[0]?.severity, 2); + }); +} + +async function siblings(files, callback) { + const root = await mkdtemp(path.join(os.tmpdir(), 'lightnvr-t10-')); + try { + const dir = path.join(root, 'js', 'components'); + await mkdir(dir, { recursive: true }); + for (const [name, body] of Object.entries(files)) await writeFile(path.join(dir, name), body); + const eslint = new ESLint({ cwd: root, overrideConfigFile: true, overrideConfig: config }); + await callback((await eslint.lintFiles(Object.keys(files).map((name) => path.join(dir, name)))).flatMap((result) => result.messages)); + } finally { await rm(root, { recursive: true, force: true }); } +} + +test('coexisting JS and TS implementations cannot double their identity', async () => { + await siblings({ 'Probe.jsx': 'export const value = 1;', 'Probe.tsx': 'export const value = 2;' }, (result) => { + assert.ok(forRule(result, duplicateRule).length >= 1, JSON.stringify(result)); + }); +}); + +test('a checked same-module JS re-export bridge is permitted', async () => { + await siblings({ 'Probe.js': '// @ts-check\nexport { value } from "./Probe.ts";', 'Probe.ts': 'export const value = 1;' }, (result) => { + assert.equal(forRule(result, duplicateRule).length, 0, JSON.stringify(result)); + }); +}); + +for (const bridge of [ + 'export { value } from "./Probe.ts";', + '// @ts-check\nexport { value } from "./Other.ts";', + '// @ts-check\nexport { value } from "./Probe.ts"; export const duplicate = 2;', + '// @ts-check\n// @ts-nocheck\nexport { value } from "./Probe.ts";', +]) { + test(`an unchecked or implementing bridge is rejected: ${bridge}`, async () => { + await siblings({ 'Probe.js': bridge, 'Probe.ts': 'export const value = 1;' }, (result) => { + assert.ok(forRule(result, duplicateRule).length >= 1, JSON.stringify(result)); + }); + }); +} + +test('inline eslint disabling cannot hide a migrated type escape', async () => { + const result = await messages('/* eslint-disable @typescript-eslint/no-explicit-any */\nexport const decode = (value: any) => value;', 'js/api/T10Probe.ts'); + assert.equal(forRule(result, '@typescript-eslint/no-explicit-any')[0]?.severity, 2); +}); diff --git a/tests/quality/localized-attributes.test.mjs b/tests/quality/localized-attributes.test.mjs new file mode 100644 index 000000000..8fa4382ba --- /dev/null +++ b/tests/quality/localized-attributes.test.mjs @@ -0,0 +1,57 @@ +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import test from 'node:test'; +import { __resetI18nStateForTests, setLocalePreference, t } from '../../web/js/i18n.js'; + +const english = JSON.parse(readFileSync(new URL('../../web/public/locales/en.json', import.meta.url), 'utf8')); +const examples = [ + ['streamsConfig.go2rtcSourcePlaceholder', {}, 'rtsp://admin:pass@camera/stream#transport=tcp\n\nOr multi-source:\n- rtsp://admin:pass@camera/main#transport=tcp\n- ffmpeg:camera1#video=h264#hardware'], + ['settings.apiDetectionDoodsPlaceholder', {}, 'http://localhost:8080/detect'], + ['settings.apiDetectionOnnxPlaceholder', {}, 'http://localhost:8000/api/v1/detect'], + ['settings.go2rtcOverridePlaceholder', {}, 'ffmpeg:\n h264_hw: "-codec:v h264_v4l2m2m"\n\nlog:\n level: trace'], + ['settings.storageTargets.objectRootPlaceholder', { bucket: 'front-door-archive' }, 's3://front-door-archive/instance'], + ['settings.storageTargets.localRootPlaceholder', {}, '/mnt/nvr-hot-01'], + ['settings.storageTargets.bucketPlaceholder', {}, 'bucket'], + ['live.plan.canvasLabel', { name: 'Floor 2' }, 'Floor 2 camera plan'], + ['live.plan.sketch.undoShortcut', { action: 'Undo drawing' }, 'Undo drawing (Ctrl+Z)'], + ['live.plan.sketch.redoShortcut', { action: 'Redo drawing' }, 'Redo drawing (Ctrl+Shift+Z)'], + ['recordings.thumbnailAlt', { stream: 'Front entrance' }, 'Front entrance recording'], + ['recordings.detectionCountOne', { count: 1 }, '1 detection'], + ['recordings.detectionCountMany', { count: 4 }, '4 detections'], + ['recordings.removeTagTitle', { tag: 'front "entrance"' }, 'Remove tag "front "entrance""'], +]; + +async function load(context, locale) { + __resetI18nStateForTests(); + context.mock.method(globalThis, 'fetch', async (url) => ({ + ok: true, + async json() { + if (url.endsWith('/manifest.json')) return { locales: [{ code: 'en' }, { code: 'fr' }] }; + if (url.endsWith('/en.json')) return english; + if (url.endsWith('/fr.json')) return { 'settings.tabsLabel': 'Sections des paramètres', 'live.plan.sketch.undo': 'Annuler le dessin', 'live.plan.sketch.redo': 'Rétablir le dessin' }; + throw new Error(`Unexpected locale URL ${url}`); + }, + })); + await setLocalePreference(locale); + context.after(() => __resetI18nStateForTests()); +} + +test('attribute keys preserve interpolation, plural labels and technical examples', async (context) => { + await load(context, 'en'); + for (const [key, params, expected] of examples) assert.equal(t(key, params), expected, key); + assert.equal(t('recordings.download'), 'Download'); + assert.equal(t('recordings.protect'), 'Protect recording'); + assert.equal(t('recordings.unprotect'), 'Unprotect recording'); +}); + +test('partial locale uses English attribute fallback without losing localized existing keys', async (context) => { + await load(context, 'fr'); + for (const [key, params, expected] of examples) assert.equal(t(key, params), expected, key); + assert.equal(t('settings.tabsLabel'), 'Sections des paramètres'); +}); + +test('fallback shortcut templates retain the already translated action', async (context) => { + await load(context, 'fr'); + assert.equal(t('live.plan.sketch.undoShortcut', { action: t('live.plan.sketch.undo') }), 'Annuler le dessin (Ctrl+Z)'); + assert.equal(t('live.plan.sketch.redoShortcut', { action: t('live.plan.sketch.redo') }), 'Rétablir le dessin (Ctrl+Shift+Z)'); +}); diff --git a/tests/quality/native-ci.test.mjs b/tests/quality/native-ci.test.mjs new file mode 100644 index 000000000..85dc3f4a8 --- /dev/null +++ b/tests/quality/native-ci.test.mjs @@ -0,0 +1,32 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import YAML from 'yaml'; + +const additions = ['test_mp4_writer_watchdog', 'test_mp4_segment_timestamps', 'test_mp4_faststart', 'test_stream_state_disposal', 'test_delete_stream_launch_failure', 'test_put_stream_launch_failure', 'test_storage_recording_faults', 'test_storage_writer_backpressure', 'test_storage_deletion_ledger', 'test_mp4_path_collision', 'test_incomplete_media_validation', 'test_mp4_pressure_reconnect', 'test_t7_16', 'test_t7_2_migrations', 'test_stale_recording_prune', 'test_t7_28', 'test_t7_53', 'test_t7_54', 'test_t7_25', 'test_t7_26', 'test_t7_55', 'test_t7_25_retention', 'test_t7_58', 'test_t7_27', 'test_t7_59', 'test_t7_59_start_failure', 'test_t7_23']; +const job = YAML.parse(fs.readFileSync('.github/workflows/integration-test.yml', 'utf8')).jobs['unity-tests']; +const build = job.steps.find(step => step.name === 'Build Unity tests').run; +const run = job.steps.find(step => step.name === 'Run Unity tests').run; +const selection = new RegExp(run.match(/-R "([^"]+)"/)[1]); +for (const target of additions) { + test('Unity CI builds and executes verified ' + target, () => { + assert.match(build, new RegExp('\\b' + target + '\\b')); + assert.ok(selection.test(target), 'missing CTest selection for ' + target); + }); +} +test('Unity CI rejects empty discovery and does not mask execution failure', () => { + assert.match(run, /--no-tests=error/); + assert.doesNotMatch(run, /\|\|\s*(true|:)|continue-on-error/); +}); + +const goJob = YAML.parse(fs.readFileSync('.github/workflows/integration-test.yml', 'utf8')).jobs['go2rtc-unit-tests']; +for (const target of ['test_t7_2_buffers', 'test_t7_2_files', 'test_t7_2_settings', 'test_t7_24']) { +test('go2rtc CI builds and executes verified advisory regression ' + target, () => { + const build = goJob.steps.find(step => step.name === 'Build go2rtc-related tests').run; + const run = goJob.steps.find(step => step.name === 'Run go2rtc-related tests').run; + assert.match(build, new RegExp('\\b' + target + '\\b')); + assert.ok(new RegExp(run.match(/-R "([^"]+)"/)[1]).test(target)); + assert.match(run, /--no-tests=error/); + assert.doesNotMatch(run, /\|\|\s*(true|:)|continue-on-error/); +}); +} diff --git a/tests/quality/test_faac_patch.py b/tests/quality/test_faac_patch.py new file mode 100644 index 000000000..c8a88dcc3 --- /dev/null +++ b/tests/quality/test_faac_patch.py @@ -0,0 +1,202 @@ +"""Exercise the real CMake/Git source preparation in disposable repositories.""" +import hashlib +import json +from pathlib import Path +import shutil +import subprocess +import tempfile +import unittest + +ROOT = Path(__file__).resolve().parents[2] +HELPER = ROOT / 'cmake/PrepareFaac.cmake' +PATCH = '0001-bound-input-pointer-arithmetic.patch' + + +def sha(data): + return hashlib.sha256(data).hexdigest() + + +def tables(folder, manifest): + raw = (json.dumps(manifest, indent=2) + '\n').encode() + (folder / 'manifest.json').write_bytes(raw) + lines = [f'set(FAAC_MANIFEST_SHA256 "{sha(raw)}")', + f'set(FAAC_UPSTREAM_COMMIT "{manifest["upstream_commit"]}")', + f'set(FAAC_PATCH_SHA256 "{manifest["patch_sha256"]}")'] + for name, key in [('FAAC_INPUT_RECORDS', 'input_files'), ('FAAC_OUTPUT_RECORDS', 'output_files')]: + lines.append(f'set({name}') + lines += [f' "{p}|{digest}"' for p, digest in sorted(manifest[key].items())] + lines.append(')') + lines.append('set(FAAC_TRANSLATION_UNITS') + lines += [f' "{p}"' for p in manifest['translation_units']] + lines.append(')') + (folder / 'manifest.cmake').write_text('\n'.join(lines) + '\n') + + +class PrepareFaacTests(unittest.TestCase): + def setUp(self): + self.tmp = tempfile.TemporaryDirectory(prefix='faac-prepare-') + self.addCleanup(self.tmp.cleanup) + self.root = Path(self.tmp.name) + self.upstream = self.root / 'upstream' + self.patchdir = self.root / 'patches' + self.build = self.root / 'build' / 'faac' + self.upstream.mkdir(); self.patchdir.mkdir(); self.build.parent.mkdir() + files = {'libfaac/frame.c': b'int frame = 1;\n', + 'libfaac/faac.c': b'int api = 1;\n', + 'libfaac/faac_internal.h': b'int internal = 1;\n', + 'include/faac.h': b'int public_api;\n', + 'COPYING': b'test license fixture\n'} + for p, content in files.items(): + f = self.upstream / p; f.parent.mkdir(exist_ok=True); f.write_bytes(content) + self.git('init', '-q'); self.git('add', '.') + self.git('-c', 'user.name=Fixture', '-c', 'user.email=fixture@example.invalid', + 'commit', '-qm', 'disposable upstream') + self.pin = self.git('rev-parse', 'HEAD').strip() + subprocess.run(['git', '-C', str(self.root), 'init', '-q'], check=True) + subprocess.run(['git', '-C', str(self.root), 'update-index', '--add', '--cacheinfo', + '160000,' + self.pin + ',upstream'], check=True) + patch = b'diff --git a/libfaac/frame.c b/libfaac/frame.c\n--- a/libfaac/frame.c\n+++ b/libfaac/frame.c\n@@ -1 +1 @@\n-int frame = 1;\n+int frame = 2;\n' + (self.patchdir / PATCH).write_bytes(patch) + self.manifest = {'schema_version': 1, 'translation_units': ['libfaac/faac.c', 'libfaac/frame.c'], 'upstream_commit': self.pin, + 'patch_sha256': sha(patch), + 'input_files': {p: sha(v) for p, v in files.items()}, + 'output_files': {p: sha(v) for p, v in files.items()}} + self.manifest['output_files']['libfaac/frame.c'] = sha(b'int frame = 2;\n') + self.save() + + def git(self, *args): + return subprocess.check_output(['git', '-C', str(self.upstream), *args], text=True, stderr=subprocess.DEVNULL) + + def save(self): + tables(self.patchdir, self.manifest) + + def run_prepare(self, cwd=None): + return subprocess.run(['cmake', f'-DFAAC_UPSTREAM_DIR={self.upstream}', + f'-DFAAC_BUILD_DIR={self.build}', f'-DFAAC_PATCH_DIR={self.patchdir}', f'-DFAAC_SUPERPROJECT_DIR={self.root}', + '-DFAAC_COMPILER=fixture-cc', '-DFAAC_BUILD_PROFILE=fixture-debug', + '-P', str(HELPER)], text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=cwd) + + def succeeds(self): + result = self.run_prepare(); self.assertEqual(result.returncode, 0, result.stdout) + return json.loads((self.build / 'provenance.json').read_text()) + + def fails(self, message): + result = self.run_prepare(); self.assertNotEqual(result.returncode, 0, result.stdout) + self.assertIn(message, result.stdout) + self.assertEqual(self.git('rev-parse', 'HEAD').strip(), self.pin) + + def test_success_is_deterministic_and_leaves_upstream_pristine(self): + first = self.succeeds() + self.assertEqual((self.build / 'libfaac/frame.c').read_text(), 'int frame = 2;\n') + self.assertEqual((self.upstream / 'libfaac/frame.c').read_text(), 'int frame = 1;\n') + self.assertEqual(self.git('status', '--porcelain'), '') + self.assertFalse((self.build / '.git').exists()) + self.assertEqual(first, self.succeeds()) + self.assertEqual(first['upstream_commit'], self.pin) + self.assertEqual(len(first['files']), 5) + for f in first['files']: + self.assertEqual(sha(Path(f['compiled_path']).read_bytes()), f['output_sha256']) + self.assertEqual(first['translation_units'], [str(self.build / 'libfaac/faac.c'), str(self.build / 'libfaac/frame.c')]) + + def test_wrong_pin_refused(self): + self.manifest['upstream_commit'] = '0' * 40; self.save(); self.fails('upstream commit') + + def test_source_drift_refused(self): + (self.upstream / 'libfaac/frame.c').write_text('changed\n'); self.fails('input hash') + + def test_unlisted_source_refused(self): + (self.upstream / 'libfaac/unknown.c').write_text('unexpected\n'); self.fails('input inventory') + + def test_json_table_disagreement_refused(self): + (self.patchdir / 'manifest.json').write_text('{}\n'); self.fails('manifest hash') + + def test_patch_hash_drift_refused(self): + with (self.patchdir / PATCH).open('ab') as f: f.write(b'changed\n') + self.fails('patch hash') + + def test_unapplicable_patch_refused(self): + p = self.patchdir / PATCH + p.write_bytes(p.read_bytes().replace(b'-int frame = 1;', b'-int frame = 9;')) + self.manifest['patch_sha256'] = sha(p.read_bytes()); self.save(); self.fails('patch check') + self.assertFalse(self.build.exists()) + + def test_wrong_postimage_refused(self): + self.manifest['output_files']['libfaac/frame.c'] = '0' * 64; self.save(); self.fails('output hash') + self.assertFalse(self.build.exists()) + + def test_manifest_path_escape_refused(self): + self.manifest['input_files']['../outside'] = '0' * 64 + self.manifest['output_files']['../outside'] = '0' * 64 + self.save(); self.fails('unsafe path') + + def test_patch_outside_allowed_scope_refused(self): + p = self.patchdir / PATCH; p.write_bytes(p.read_bytes().replace(b'libfaac/frame.c', b'include/faac.h')) + self.manifest['patch_sha256'] = sha(p.read_bytes()); self.save(); self.fails('patch path') + + def test_source_symlink_refused(self): + p = self.upstream / 'libfaac/frame.c'; p.unlink(); p.symlink_to(self.upstream / 'libfaac/faac.c') + self.fails('symlink') + + def test_output_symlink_refused(self): + self.build.symlink_to(self.upstream, target_is_directory=True); self.fails('symlink') + + def test_output_ancestor_symlink_refused(self): + self.build.parent.rmdir(); self.build.parent.symlink_to(self.upstream, target_is_directory=True) + self.fails('symlink') + + def test_output_tamper_refused_and_not_destroyed(self): + self.succeeds(); p = self.build / 'libfaac/frame.c'; p.write_text('do not erase\n') + self.fails('output hash'); self.assertEqual(p.read_text(), 'do not erase\n') + + def test_extra_stale_output_refused(self): + self.succeeds(); (self.build / 'libfaac/stale.c').write_text('stale\n'); self.fails('output inventory') + + def test_output_manifest_tamper_refused(self): + self.succeeds(); (self.build / 'provenance.json').write_text('{}\n'); self.fails('provenance') + + def test_missing_output_refused(self): + self.succeeds(); (self.build / 'include/faac.h').unlink(); self.fails('output inventory') + + def test_build_destination_may_not_overlap_upstream(self): + self.build = self.upstream / 'copy'; self.fails('overlap') + + def test_incremental_build_cwd_does_not_rewrite_patch_paths(self): + result = self.run_prepare(cwd=self.build.parent) + self.assertEqual(result.returncode, 0, result.stdout) + again = self.run_prepare(cwd=self.build.parent) + self.assertEqual(again.returncode, 0, again.stdout) + + def test_unused_source_is_copied_but_not_claimed_as_compiled(self): + path = 'libfaac/optional.c'; data = b'int unused;\n' + (self.upstream / path).write_bytes(data) + self.manifest['input_files'][path] = sha(data) + self.manifest['output_files'][path] = sha(data) + self.save() + actual = self.succeeds() + self.assertTrue((self.build / path).exists()) + self.assertNotIn(str(self.build / path), actual['translation_units']) + + def test_wrong_gitlink_refused(self): + subprocess.run(['git', '-C', str(self.root), 'update-index', '--add', '--cacheinfo', + '160000,' + 'f' * 40 + ',upstream'], check=True) + self.fails('gitlink') + + def test_literal_manifest_symlink_refused(self): + p = self.patchdir / 'manifest.cmake' + real = self.root / 'table'; p.rename(real); p.symlink_to(real) + self.fails('symlink') + + def test_json_manifest_symlink_refused(self): + p = self.patchdir / 'manifest.json' + real = self.root / 'json'; p.rename(real); p.symlink_to(real) + self.fails('symlink') + + def test_committed_json_and_literal_tables_are_identical(self): + manifest = json.loads((ROOT / 'patches/faac/manifest.json').read_text()) + expected = self.root / 'expected'; expected.mkdir(); tables(expected, manifest) + self.assertEqual((expected / 'manifest.cmake').read_bytes(), + (ROOT / 'patches/faac/manifest.cmake').read_bytes()) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/quality/test_findings.py b/tests/quality/test_findings.py new file mode 100644 index 000000000..fd7e016d1 --- /dev/null +++ b/tests/quality/test_findings.py @@ -0,0 +1,663 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +"""Tests for scripts/quality/findings.py (T6: finding identity and exception policy). + +Run: python3 -m unittest discover -s tests/quality -p test_findings.py +The fixture inventory in fixtures/findings/base was generated with the T1 +generator's fingerprint formulas, independently of findings.py, so a passing +baseline proves the checker reproduces the T1 identity scheme. +""" +import contextlib +import hashlib +import io +import json +import os +import shutil +import subprocess +import sys +import tempfile +import unittest +from pathlib import Path + +HERE = Path(__file__).resolve().parent +REPO = HERE.parent.parent +SCRIPT = REPO / "scripts" / "quality" / "findings.py" +BASE = HERE / "fixtures" / "findings" / "base" +ESLINT_LOG = HERE / "fixtures" / "findings" / "eslint-log" / "web-jsx-a11y.log" + +sys.path.insert(0, str(SCRIPT.parent)) +import findings # noqa: E402 (RED: fails until scripts/quality/findings.py exists) + +TODAY = "2026-11-01" +CT_REL = "actions/static-analysis-100/clang-tidy-report/clang-tidy-report.txt" +CC_REL = "actions/static-analysis-100/cppcheck-reports/cppcheck-report.xml" +ES_REL = "actions/static-analysis-100/eslint-web.json" +TRIVY_REL = "actions/container-scan-200/trivy-results.sarif" +CODEQL_REL = "github/codeql-abcdef12.sarif" + + +def sha(path): + return hashlib.sha256(Path(path).read_bytes()).hexdigest() + + +class Case(unittest.TestCase): + def setUp(self): + self.tmp = Path(tempfile.mkdtemp(prefix="t6-")) + shutil.copytree(BASE, self.tmp / "base") + self.d = self.tmp / "base" + self.reports = self.d / "reports" + + def tearDown(self): + shutil.rmtree(self.tmp, ignore_errors=True) + + # helpers ------------------------------------------------------------- + def load(self, name): + return json.loads((self.d / name).read_text()) + + def save(self, name, obj): + (self.d / name).write_text(json.dumps(obj, indent=2)) + + def rj(self, rel): + return json.loads((self.reports / rel).read_text()) + + def wj(self, rel, obj): + p = self.reports / rel + p.parent.mkdir(parents=True, exist_ok=True) + p.write_text(json.dumps(obj, indent=2)) + + def evaluate(self, today=TODAY, release=False): + return findings.evaluate( + reports_dir=self.reports, + inventory=self.load("inventory.json"), + scanners=self.load("scanners.json"), + policy=self.load("policy.json"), + today=today, + release=release, + reports_key="fixture/reports", + ) + + def kinds(self, result): + return sorted({f["kind"] for f in result["failures"]}) + + def cli(self, *extra, today=TODAY): + cmd = [sys.executable, "-I", str(SCRIPT), "--reports", str(self.reports), + "--inventory", str(self.d / "inventory.json"), + "--scanners", str(self.d / "scanners.json"), + "--policy", str(self.d / "policy.json"), "--today", today, *extra] + return subprocess.run(cmd, capture_output=True, text=True, cwd=str(REPO)) + + +class BaselineTests(Case): + def test_baseline_passes(self): + r = self.evaluate() + self.assertEqual(r["failures"], [], r["failures"]) + self.assertEqual(r["status"], "pass") + for sid, s in r["scanners"].items(): + self.assertEqual(s["status"], "ok", (sid, s)) + + def test_cli_check_exit_zero_on_pass(self): + p = self.cli("--check") + self.assertEqual(p.returncode, 0, p.stdout + p.stderr) + self.assertIn("RESULT: PASS", p.stdout) + + def test_valid_empty_report_passes(self): + r = self.evaluate() + trivy = r["scanners"]["trivy-container"] + self.assertEqual(trivy["status"], "ok") + self.assertEqual(trivy["observed"]["itemized"], 0) + self.assertTrue(trivy["valid_empty"]) + + +class NewFindingTests(Case): + def test_new_finding_fails_even_when_another_disappears(self): + s = self.rj(CODEQL_REL) + s["runs"][0]["results"][0]["ruleId"] = "cpp/uncontrolled-allocation-size" + s["runs"][0]["results"][0]["partialFingerprints"]["primaryLocationLineHash"] = "9999888877776666:1" + self.wj(CODEQL_REL, s) + r = self.evaluate() + self.assertEqual(r["status"], "fail") + new = [f for f in r["failures"] if f["kind"] == "new-finding"] + self.assertEqual(len(new), 1) + self.assertIn("cpp/uncontrolled-allocation-size", new[0]["fingerprint"]) + resolved = [i for i in r["info"] if i["kind"] == "resolved"] + self.assertTrue(any("cpp/sql-injection" in i["fingerprint"] for i in resolved)) + # result counts are equal; the checker must not net them out + self.assertEqual(r["scanners"]["codeql-cpp"]["observed"]["itemized"], 2) + + def test_new_clang_tidy_message_fails(self): + p = self.reports / CT_REL + p.write_text(p.read_text() + "/__w/lightNVR/lightNVR/src/core/c.c:44:1: warning: " + "Call to function 'strcpy' is insecure [clang-analyzer-security.insecureAPI.strcpy]\n") + r = self.evaluate() + self.assertIn("new-finding", self.kinds(r)) + + def test_bucket_increase_fails_even_if_other_bucket_decreases(self): + es = self.rj(ES_REL) + msgs = es[0]["messages"] + msgs.append(dict(msgs[0], line=20)) # click-events +1 + es[0]["messages"] = [m for m in msgs if m["ruleId"] != "jsx-a11y/no-autofocus"] # autofocus -1 + self.wj(ES_REL, es) + r = self.evaluate() + self.assertEqual(r["status"], "fail") + inc = [f for f in r["failures"] if f["kind"] == "bucket-increase"] + self.assertEqual(len(inc), 1) + self.assertIn("click-events-have-key-events", inc[0]["fingerprint"]) + self.assertIn("2 -> 3", inc[0]["detail"]) + self.assertTrue(any(i["kind"] in ("bucket-decrease", "resolved") for i in r["info"])) + + def test_new_bucket_fails(self): + es = self.rj(ES_REL) + es[1]["messages"].append({"ruleId": "jsx-a11y/alt-text", "severity": 1, "message": "m", "line": 1, "column": 1}) + self.wj(ES_REL, es) + r = self.evaluate() + self.assertIn("bucket-new", self.kinds(r)) + + +class IdentityTests(Case): + def test_line_shift_keeps_fingerprints(self): + before = self.evaluate() + import re + ct = self.reports / CT_REL + ct.write_text(re.sub(r"(\.c):(\d+):", lambda m: f"{m[1]}:{int(m[2]) + 7}:", ct.read_text())) + cc = self.reports / CC_REL + cc.write_text(re.sub(r'line="(\d+)"', lambda m: f'line="{int(m[1]) + 7}"', cc.read_text())) + s = self.rj(CODEQL_REL) + for res in s["runs"][0]["results"]: + res["locations"][0]["physicalLocation"]["region"]["startLine"] += 7 + self.wj(CODEQL_REL, s) + after = self.evaluate() + self.assertEqual(after["failures"], []) + for sid in ("clang-tidy", "cppcheck", "codeql-cpp"): + self.assertEqual(before["scanners"][sid]["fingerprints"], after["scanners"][sid]["fingerprints"], sid) + + def test_line_tool_fingerprint_matches_t1_scheme(self): + got = findings.line_tool_fingerprint("clang-tidy", "bugprone-x", "src/a.c", "value 42 is 7", 1) + h = hashlib.sha256("bugprone-x|src/a.c|value N is N|1".encode()).hexdigest()[:16] + self.assertEqual(got, f"clang-tidy:bugprone-x:src/a.c:{h}") + + def test_rename_requires_explicit_policy_map(self): + ct = self.reports / CT_REL + ct.write_text(ct.read_text().replace("src/core/b.c", "src/core/b_renamed.c")) + r = self.evaluate() + self.assertIn("new-finding", self.kinds(r)) + pol = self.load("policy.json") + pol["path_renames"] = [{"from": "src/core/b.c", "to": "src/core/b_renamed.c", + "reason": "test rename", "approved_by": "test"}] + self.save("policy.json", pol) + r = self.evaluate() + self.assertEqual(r["failures"], [], r["failures"]) + self.assertTrue(any(i["kind"] == "renamed" for i in r["info"])) + + def test_directory_rename_prefix(self): + es = self.rj(ES_REL) + es[0]["filePath"] = es[0]["filePath"].replace("web/js/x.jsx", "web/src/x.jsx") + self.wj(ES_REL, es) + pol = self.load("policy.json") + pol["path_renames"] = [{"from": "web/js/", "to": "web/src/", "reason": "dir move", "approved_by": "test"}] + self.save("policy.json", pol) + self.assertEqual(self.evaluate()["failures"], []) + + +class CoverageTests(Case): + def test_missing_report_fails(self): + (self.reports / TRIVY_REL).unlink() + r = self.evaluate() + self.assertEqual(r["status"], "fail") + self.assertIn("missing-report", self.kinds(r)) + self.assertEqual(r["scanners"]["trivy-container"]["status"], "fail") + + def test_missing_one_variant_of_brace_convention_fails(self): + (self.reports / "npm/web-audit-prod.json").unlink() + r = self.evaluate() + self.assertIn("missing-report", self.kinds(r)) + + def test_ambiguous_report_fails_unless_pinned(self): + shutil.copy(self.reports / CODEQL_REL, self.reports / "github/codeql-12345678.sarif") + r = self.evaluate() + self.assertIn("ambiguous-report", self.kinds(r)) + pol = self.load("policy.json") + pol["report_pins"] = [{"reports_dir": "fixture/reports", "scanner": "codeql-cpp", + "paths": [CODEQL_REL], "reason": "test"}] + self.save("policy.json", pol) + self.assertEqual(self.evaluate()["failures"], []) + + def test_crashed_clang_tidy_fails(self): + ct = self.reports / CT_REL + lines = ct.read_text().splitlines() + cut = [l for l in lines if not l.startswith("[2/2]")][:5] + ct.write_text("\n".join(cut + ["Stack dump:", "Segmentation fault (core dumped)"]) + "\n") + r = self.evaluate() + self.assertIn("scanner-error", self.kinds(r)) + + def test_truncated_clang_tidy_fails(self): + ct = self.reports / CT_REL + ct.write_text("".join(l for l in ct.read_text().splitlines(True) if "[2/2]" not in l)) + r = self.evaluate() + self.assertIn("truncated-report", self.kinds(r)) + + def test_clang_diagnostic_error_is_scanner_error(self): + ct = self.reports / CT_REL + ct.write_text(ct.read_text() + "/__w/lightNVR/lightNVR/src/core/c.c:1:10: error: 'foo.h' file not found [clang-diagnostic-error]\n") + self.assertIn("scanner-error", self.kinds(self.evaluate())) + + def test_empty_clang_tidy_report_is_empty_scope(self): + (self.reports / CT_REL).write_text("") + r = self.evaluate() + self.assertIn("empty-scope", self.kinds(r)) + + def test_trivy_without_target_is_empty_scope(self): + t = self.rj(TRIVY_REL) + t["runs"][0]["properties"] = {} + self.wj(TRIVY_REL, t) + self.assertIn("empty-scope", self.kinds(self.evaluate())) + + def test_eslint_json_with_no_files_is_empty_scope(self): + self.wj(ES_REL, []) + self.assertIn("empty-scope", self.kinds(self.evaluate())) + + def test_eslint_fatal_parse_error_is_scanner_error(self): + es = self.rj(ES_REL) + es[1]["messages"].append({"ruleId": None, "fatal": True, "severity": 2, "message": "Parsing error"}) + self.wj(ES_REL, es) + self.assertIn("scanner-error", self.kinds(self.evaluate())) + + def test_npm_zero_dependencies_is_empty_scope(self): + a = self.rj("npm/web-audit-prod.json") + a["metadata"]["dependencies"]["total"] = 0 + self.wj("npm/web-audit-prod.json", a) + self.assertIn("empty-scope", self.kinds(self.evaluate())) + + def test_empty_cppcheck_is_empty_scope(self): + (self.reports / CC_REL).write_text('\n\n') + self.assertIn("empty-scope", self.kinds(self.evaluate())) + + def test_cppcheck_critical_errors_marker_is_scanner_error(self): + cc = self.reports / CC_REL + cc.write_text(cc.read_text().replace("Active checkers: 106/592", "Active checkers: There was critical errors")) + self.assertIn("scanner-error", self.kinds(self.evaluate())) + + def test_unparseable_report_is_invalid_not_crash(self): + (self.reports / CODEQL_REL).write_text("{not json") + r = self.evaluate() + self.assertIn("invalid-report", self.kinds(r)) + + def test_sarif_execution_failure_is_scanner_error(self): + s = self.rj(CODEQL_REL) + s["runs"][0]["invocations"] = [{"executionSuccessful": False, + "toolExecutionNotifications": [{"level": "error", "message": {"text": "extractor crashed"}}]}] + self.wj(CODEQL_REL, s) + self.assertIn("scanner-error", self.kinds(self.evaluate())) + + def test_sarif_wrong_category_is_scope_mismatch(self): + s = self.rj(CODEQL_REL) + s["runs"][0]["automationDetails"]["id"] = "/language:javascript-typescript/" + self.wj(CODEQL_REL, s) + self.assertIn("scope-mismatch", self.kinds(self.evaluate())) + + def test_api_error_body_is_not_an_empty_result(self): + self.wj("github/dependabot-open.json", {"message": "Dependabot alerts are disabled for this repository.", "status": "403"}) + r = self.evaluate() + self.assertIn("scanner-error", self.kinds(r)) + + def test_unconfigured_scanner_is_not_scanned(self): + sc = self.load("scanners.json") + sc["scanners"].append({"id": "secret-scanning", "report_format": "json", "access_status": "disabled", + "report_path_convention": "build/eng-artifacts//github/secret-scanning-alerts.json", + "fingerprint_scheme": "secret"}) + self.save("scanners.json", sc) + r = self.evaluate() + self.assertIn("not-scanned", self.kinds(r)) + self.assertEqual(r["status"], "fail") + + +class ExitStatusTests(Case): + def manifest(self, entries): + self.wj("run-manifest.json", {"schema_version": 1, "revision": "abcdef12", "scanners": entries}) + + def test_npm_error_key_fails_even_if_exit_zero(self): + self.wj("npm/web-audit-all.json", {"error": {"code": "ENOLOCK", "summary": "lockfile missing"}}) + self.manifest({"npm-audit-web": {"exit_code": 0, "status": "success"}}) + r = self.evaluate() + self.assertIn("scanner-error", self.kinds(r)) + p = self.cli("--check") + self.assertEqual(p.returncode, 1, p.stdout) + + def test_npm_exit_one_with_findings_is_not_error(self): + self.manifest({"npm-audit-web": {"exit_code": 1, "status": "success"}}) + self.assertEqual(self.evaluate()["failures"], []) + + def test_recorded_failure_status_fails_despite_valid_report(self): + self.manifest({"trivy-container": {"exit_code": 0, "status": "failure"}}) + r = self.evaluate() + self.assertIn("scanner-error", self.kinds(r)) + + def test_recorded_nonzero_exit_fails(self): + self.manifest({"clang-tidy": {"exit_code": 139, "status": "success"}}) + self.assertIn("scanner-error", self.kinds(self.evaluate())) + + def test_manifest_files_scanned_zero_is_empty_scope(self): + self.manifest({"codeql-cpp": {"exit_code": 0, "status": "success", "files_scanned": 0}}) + self.assertIn("empty-scope", self.kinds(self.evaluate())) + + def test_required_manifest_missing_fails(self): + pol = self.load("policy.json") + pol["require_run_manifest"] = True + self.save("policy.json", pol) + self.assertIn("missing-run-manifest", self.kinds(self.evaluate())) + + def test_internal_error_never_exits_zero(self): + orig = findings.evaluate + try: + def boom(**kw): + raise RuntimeError("synthetic checker bug") + findings.evaluate = boom + err = io.StringIO() + with contextlib.redirect_stderr(err), contextlib.redirect_stdout(io.StringIO()): + rc = findings.main(["--check", "--reports", str(self.reports), + "--inventory", str(self.d / "inventory.json"), + "--scanners", str(self.d / "scanners.json"), + "--policy", str(self.d / "policy.json"), "--today", TODAY]) + finally: + findings.evaluate = orig + self.assertEqual(rc, 3) + self.assertIn("synthetic checker bug", err.getvalue()) + + def test_missing_reports_dir_is_nonzero(self): + p = subprocess.run([sys.executable, "-I", str(SCRIPT), "--check", "--reports", str(self.tmp / "nope"), + "--inventory", str(self.d / "inventory.json"), "--scanners", str(self.d / "scanners.json"), + "--policy", str(self.d / "policy.json"), "--today", TODAY], capture_output=True, text=True) + self.assertNotEqual(p.returncode, 0) + + +class ExceptionTests(Case): + def test_unexpired_exception_passes(self): + self.assertEqual(self.evaluate(today="2027-01-31")["failures"], []) # expiry day is still valid + + def test_exception_requires_recorded_approval(self): + for collection in ("findings", "buckets"): + for approval in (None, {}, {"status": "proposed"}, {"status": "approved"}): + with self.subTest(collection=collection, approval=approval): + inv = self.load("inventory.json") + row = next(r for r in inv[collection] if r["disposition"] == "baseline-debt") + row["approval"] = approval + self.save("inventory.json", inv) + self.assertIn("inventory-error", self.kinds(self.evaluate())) + + def test_exception_approval_fields_are_nonempty_strings(self): + original = self.load("inventory.json") + for key in ("approved_by", "approved_on", "evidence"): + for value in (None, " ", [], 42): + with self.subTest(key=key, value=value): + inv = json.loads(json.dumps(original)) + inv["findings"][1]["approval"][key] = value + self.save("inventory.json", inv) + self.assertIn("inventory-error", self.kinds(self.evaluate())) + + def test_exception_requires_owner_reason_and_valid_approval_date(self): + original = self.load("inventory.json") + for field in ("owner", "reason"): + inv = json.loads(json.dumps(original)) + inv["findings"][1][field] = " " + self.save("inventory.json", inv) + self.assertIn("inventory-error", self.kinds(self.evaluate())) + for date in ("invalid", "2026-11-02"): + inv = json.loads(json.dumps(original)) + inv["findings"][1]["approval"]["approved_on"] = date + self.save("inventory.json", inv) + self.assertIn("inventory-error", self.kinds(self.evaluate())) + + def test_baseline_update_cannot_approve_an_exception(self): + inv = self.load("inventory.json") + del inv["findings"][1]["approval"] + self.save("inventory.json", inv) + before = sha(self.d / "inventory.json") + result = self.cli("--update-baseline", "--reason", "synthetic attempt") + self.assertNotEqual(result.returncode, 0) + self.assertEqual(sha(self.d / "inventory.json"), before) + + def test_expired_exception_fails(self): + r = self.evaluate(today="2027-02-01") + exp = [f for f in r["failures"] if f["kind"] == "expired-exception"] + self.assertEqual(len(exp), 1) + self.assertIn("cpp/path-injection", exp[0]["fingerprint"]) + p = self.cli("--check", today="2027-02-01") + self.assertEqual(p.returncode, 1) + + def test_exception_without_expiry_is_inventory_error(self): + inv = self.load("inventory.json") + inv["buckets"][0]["exception_expiry"] = None + self.save("inventory.json", inv) + self.assertIn("inventory-error", self.kinds(self.evaluate())) + + def test_unknown_disposition_is_inventory_error(self): + inv = self.load("inventory.json") + inv["findings"][0]["disposition"] = "ignore-forever" + self.save("inventory.json", inv) + self.assertIn("inventory-error", self.kinds(self.evaluate())) + + def test_duplicate_fingerprint_is_inventory_error(self): + inv = self.load("inventory.json") + inv["findings"].append(dict(inv["findings"][0], id="DUP")) + self.save("inventory.json", inv) + self.assertIn("inventory-error", self.kinds(self.evaluate())) + + def test_release_mode_blocks_untriaged(self): + r = self.evaluate(release=True) + self.assertIn("release-blocker", self.kinds(r)) + + +class GapPolicyTests(Case): + def add_secret_scanner(self): + sc = self.load("scanners.json") + sc["scanners"].append({"id": "secret-scanning", "report_format": "json", "access_status": "disabled", + "report_path_convention": "build/eng-artifacts//github/secret-scanning-alerts.json", + "fingerprint_scheme": "secret"}) + self.save("scanners.json", sc) + + def gap(self, expires): + pol = self.load("policy.json") + pol["accepted_gaps"] = [{"scanner": "secret-scanning", "condition": "not-scanned", "owner": "maintainers/security", + "expires": expires, "reason": "disabled upstream", "coverage_gap_ref": "GAP-x", + "approval": {"status": "approved", "approved_by": "fixture-reviewer", + "approved_on": "2026-09-01", "evidence": "synthetic:test-review"}}] + self.save("policy.json", pol) + + def test_accepted_gap_is_visible_and_passes(self): + self.add_secret_scanner() + self.gap("2026-12-31") + r = self.evaluate() + self.assertEqual(r["failures"], []) + self.assertEqual(r["scanners"]["secret-scanning"]["status"], "accepted-gap") + self.assertEqual(len(r["accepted_gaps"]), 1) + p = self.cli("--check") + self.assertEqual(p.returncode, 0) + self.assertIn("ACCEPTED GAP", p.stdout) + self.assertIn("NOT SCANNED", p.stdout) + + def test_unapproved_gaps_cannot_hide_missing_scans(self): + self.add_secret_scanner() + for approval in (None, {}, {"status": "proposed"}, {"status": "approved"}): + with self.subTest(approval=approval): + self.gap("2026-12-31") + pol = self.load("policy.json") + pol["accepted_gaps"][0]["approval"] = approval + self.save("policy.json", pol) + r = self.evaluate() + self.assertIn("policy-error", self.kinds(r)) + self.assertIn("not-scanned", self.kinds(r)) + self.assertEqual(r["accepted_gaps"], []) + + def test_proposed_status_conflicts_with_approval(self): + self.add_secret_scanner() + self.gap("2026-12-31") + pol = self.load("policy.json") + pol["accepted_gaps"][0]["status"] = "proposed-by-T6" + self.save("policy.json", pol) + self.assertIn("policy-error", self.kinds(self.evaluate())) + + def test_gap_future_approval_cannot_hide_missing_scan(self): + self.add_secret_scanner() + self.gap("2026-12-31") + pol = self.load("policy.json") + pol["accepted_gaps"][0]["approval"]["approved_on"] = "2026-11-02" + self.save("policy.json", pol) + self.assertIn("not-scanned", self.kinds(self.evaluate())) + + def test_expired_gap_fails(self): + self.add_secret_scanner() + self.gap("2026-10-01") + r = self.evaluate() + self.assertIn("not-scanned", self.kinds(r)) + self.assertTrue(any("expired" in f["detail"] for f in r["failures"])) + + def test_release_mode_ignores_accepted_gaps(self): + self.add_secret_scanner() + self.gap("2026-12-31") + self.assertIn("not-scanned", self.kinds(self.evaluate(release=True))) + + def test_gap_without_owner_is_policy_error(self): + self.add_secret_scanner() + self.gap("2026-12-31") + pol = self.load("policy.json") + del pol["accepted_gaps"][0]["owner"] + self.save("policy.json", pol) + self.assertIn("policy-error", self.kinds(self.evaluate())) + + def test_gap_scoped_to_other_reports_dir_does_not_apply(self): + self.add_secret_scanner() + self.gap("2026-12-31") + pol = self.load("policy.json") + pol["accepted_gaps"][0]["reports_dirs"] = ["build/eng-artifacts/T1"] + self.save("policy.json", pol) + self.assertIn("not-scanned", self.kinds(self.evaluate())) + + +class ProvenanceTests(Case): + def test_tool_version_change_is_surfaced(self): + s = self.rj(CODEQL_REL) + s["runs"][0]["tool"]["driver"]["semanticVersion"] = "2.28.0" + self.wj(CODEQL_REL, s) + r = self.evaluate() + self.assertEqual(r["failures"], []) + ch = [c for c in r["provenance_changes"] if c["scanner"] == "codeql-cpp"] + self.assertEqual(len(ch), 1) + self.assertEqual((ch[0]["expected"], ch[0]["observed"]), ("2.27.3", "2.28.0")) + p = self.cli("--check") + self.assertIn("PROVENANCE CHANGED", p.stdout) + self.assertIn("2.27.3 -> 2.28.0", p.stdout) + + def test_db_version_from_manifest_is_reported(self): + self.wj("run-manifest.json", {"schema_version": 1, "scanners": { + "trivy-container": {"exit_code": 0, "status": "success", "db_version": "2026-10-31T00:00:00Z"}}}) + r = self.evaluate() + self.assertEqual(r["scanners"]["trivy-container"]["provenance"]["db_version"], "2026-10-31T00:00:00Z") + + +class FormatTests(Case): + def test_eslint_stylish_log_matches_json_buckets(self): + (self.reports / ES_REL).unlink() + shutil.copy(ESLINT_LOG, self.reports / "actions/static-analysis-100/eslint-web.json.log") + sc = self.load("scanners.json") + for s in sc["scanners"]: + if s["id"] == "eslint-web": + s["report_path_convention"] = "build/eng-artifacts//actions/static-analysis-/eslint-web.json.log" + s["report_format"] = "eslint stylish text in job log only (no artifact)" + self.save("scanners.json", sc) + self.assertEqual(self.evaluate()["failures"], []) + + def test_eslint_log_summary_mismatch_is_invalid(self): + text = ESLINT_LOG.read_text().replace("3 problems (0 errors, 3 warnings)", "4 problems (0 errors, 4 warnings)") + (self.reports / ES_REL).unlink() + (self.reports / "actions/static-analysis-100/eslint-web.json.log").write_text(text) + sc = self.load("scanners.json") + for s in sc["scanners"]: + if s["id"] == "eslint-web": + s["report_path_convention"] = "build/eng-artifacts//actions/static-analysis-/eslint-web.json.log" + s["report_format"] = "eslint stylish text in job log only (no artifact)" + self.save("scanners.json", sc) + self.assertIn("invalid-report", self.kinds(self.evaluate())) + + def test_npm_prod_exposure_escalation_fails(self): + self.wj("npm/web-audit-prod.json", self.rj("npm/web-audit-all.json")) + self.assertIn("exposure-escalation", self.kinds(self.evaluate())) + + +class BaselineEditTests(Case): + def add_new_codeql(self): + s = self.rj(CODEQL_REL) + new = json.loads(json.dumps(s["runs"][0]["results"][0])) + new["ruleId"] = "cpp/toctou-race-condition" + new["partialFingerprints"]["primaryLocationLineHash"] = "1234123412341234:1" + s["runs"][0]["results"].append(new) + self.wj(CODEQL_REL, s) + + def test_check_never_modifies_inventory(self): + self.add_new_codeql() + inv = self.d / "inventory.json" + before = sha(inv) + p = self.cli("--check") + self.assertEqual(p.returncode, 1, p.stdout) + self.assertEqual(sha(inv), before) + self.assertEqual(self.cli("--check").returncode, 1) # still failing: nothing auto-baselined + + def test_update_baseline_requires_reason(self): + self.add_new_codeql() + p = self.cli("--update-baseline") + self.assertNotEqual(p.returncode, 0) + + def test_update_baseline_is_explicit_and_recorded(self): + self.add_new_codeql() + p = self.cli("--update-baseline", "--reason", "accept new toctou for triage (test)") + self.assertEqual(p.returncode, 0, p.stdout + p.stderr) + inv = self.load("inventory.json") + rec = [f for f in inv["findings"] if "toctou" in f["fingerprint"]] + self.assertEqual(len(rec), 1) + self.assertEqual(rec[0]["disposition"], "needs-triage") + self.assertEqual(inv["baseline_edits"][-1]["reason"], "accept new toctou for triage (test)") + self.assertEqual(self.cli("--check").returncode, 0) + + def test_update_baseline_refuses_on_coverage_failure(self): + (self.reports / TRIVY_REL).unlink() + inv = self.d / "inventory.json" + before = sha(inv) + p = self.cli("--update-baseline", "--reason", "x") + self.assertNotEqual(p.returncode, 0) + self.assertEqual(sha(inv), before) + + def test_check_and_update_are_mutually_exclusive(self): + p = self.cli("--check", "--update-baseline", "--reason", "x") + self.assertEqual(p.returncode, 2) + + +class RequiredManifestTests(Case): + def require_manifest(self): + policy = self.load("policy.json") + policy["require_run_manifest"] = True + self.save("policy.json", policy) + + def test_required_provenance_needs_explicit_revision(self): + self.require_manifest() + self.wj("run-manifest.json", {"schema_version": 1, "revision": "a" * 40, "scanners": {}}) + result = self.evaluate() + self.assertTrue(any("expected-revision" in item["detail"] for item in result["failures"])) + + def test_malformed_manifest_root_is_failure_not_internal_error(self): + self.require_manifest() + self.wj("run-manifest.json", []) + result = self.evaluate() + self.assertIn("invalid-run-manifest", self.kinds(result)) + self.assertEqual(self.cli("--check", "--expected-revision", "a" * 40).returncode, 1) + + def test_manifest_symlink_is_rejected_before_read(self): + outside = self.tmp / "outside.json" + outside.write_text('{"scanners": {}, "private": "not report data"}') + (self.reports / "run-manifest.json").symlink_to(outside) + self.assertIn("invalid-run-manifest", self.kinds(self.evaluate())) + + def test_unrequired_manifest_also_rejects_traversal_report_paths(self): + self.wj("run-manifest.json", {"scanners": {"eslint-web": {"reports": ["../inventory.json"]}}}) + result = self.evaluate() + self.assertIn("invalid-run-manifest", self.kinds(result)) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/quality/test_runners.py b/tests/quality/test_runners.py new file mode 100644 index 000000000..1e96f44a2 --- /dev/null +++ b/tests/quality/test_runners.py @@ -0,0 +1,251 @@ +"""Ownership and fail-closed tests for the engineering test runners.""" +import importlib.util +import json +import os +from pathlib import Path +import subprocess +import sys +import tempfile +import unittest + +ROOT = Path(__file__).resolve().parents[2] +SPEC = importlib.util.spec_from_file_location('native_runner', ROOT / 'scripts/quality/run-native.py') +native = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(native) + + +class NativeRunnerTests(unittest.TestCase): + def test_task_id_rejects_path_escape(self): + for value in ('../other', '', '/tmp/task', 'T1/../../other', 'T1;touch x'): + with self.subTest(value=value), self.assertRaises(ValueError): + native.validate_task(value) + self.assertEqual(native.validate_task('T31.3'), 'T31.3') + + def test_empty_selection_fails(self): + with self.assertRaises(ValueError): + native.test_names({'tests': []}) + + def test_invalid_selection_fails(self): + for payload in ({}, {'tests': [{'name': '../escape'}]}, {'tests': [{'name': ''}]}, {'tests': 'bad'}): + with self.subTest(payload=payload), self.assertRaises(ValueError): + native.test_names(payload) + + def test_selected_names_preserved(self): + self.assertEqual(native.test_names({'tests': [{'name': 'test_system_health_proc'}]}), ['test_system_health_proc']) + + def test_lock_serializes_distinct_runs(self): + with tempfile.TemporaryDirectory() as directory: + lock = Path(directory) / 'lock' + with native.runtime_lock(lock): + with self.assertRaises(FileExistsError): + with native.runtime_lock(lock): + self.fail('second lock acquired') + self.assertFalse(lock.exists()) + + def test_lock_never_removes_replaced_owner(self): + with tempfile.TemporaryDirectory() as directory: + lock = Path(directory) / 'lock' + with self.assertRaises(RuntimeError): + with native.runtime_lock(lock): + (lock / 'owner').write_text('other-owner') + self.assertEqual((lock / 'owner').read_text(), 'other-owner') + + def test_subprocess_failure_propagates(self): + with tempfile.TemporaryDirectory() as directory: + with self.assertRaises(subprocess.CalledProcessError) as raised: + native.run_command([sys.executable, '-c', 'raise SystemExit(7)'], Path(directory) / 'log') + self.assertEqual(raised.exception.returncode, 7) + + def test_dependency_env_excludes_stale_local_paths(self): + env = native.build_env({'PKG_CONFIG_PATH': '/usr/local/lib/pkgconfig', 'CFLAGS': '-I/usr/local/include', 'LIGHTNVR_URL': 'https://production.invalid'}) + self.assertEqual(env['PKG_CONFIG_PATH'], '') + self.assertNotIn('CFLAGS', env) + self.assertNotIn('LIGHTNVR_URL', env) + self.assertNotIn('/usr/local', env['PKG_CONFIG_LIBDIR']) + + +class IntegrationRunnerTests(unittest.TestCase): + def node(self, code): + result = subprocess.run(['node', '--input-type=module', '-e', code], cwd=ROOT, text=True, capture_output=True) + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + + def test_loopback_rejects_remote_and_credentials(self): + self.node(""" + import assert from 'node:assert/strict'; + import { validateLoopback } from './scripts/quality/run-integration.mjs'; + for (const url of ['https://production.invalid', 'http://127.0.0.1.evil', 'http://user:pass@localhost:18080', 'file:///tmp/server']) { + assert.throws(() => validateLoopback(url)); + } + for (const url of ['http://localhost:18080', 'http://127.0.0.1:18080', 'http://[::1]:18080']) validateLoopback(url); + """) + + def test_integration_lock_uses_same_exclusive_lease(self): + with tempfile.TemporaryDirectory() as directory: + lock = Path(directory) / 'lock' + with native.runtime_lock(lock): + self.node(f""" + import assert from 'node:assert/strict'; + import {{ acquireLock }} from './scripts/quality/run-integration.mjs'; + assert.throws(() => acquireLock({json.dumps(str(lock))})); + """) + + def test_selection_zero_and_invalid_are_failure(self): + self.node(""" + import assert from 'node:assert/strict'; + import { selectedCount } from './scripts/quality/run-integration.mjs'; + assert.throws(() => selectedCount({suites: []})); + assert.throws(() => selectedCount({})); + assert.equal(selectedCount({suites: [{suites: [], specs: [{tests: [{projectName: 'api'}]}]}]}), 1); + """) + + def test_ownership_rejects_missing_and_forged_token_without_deleting(self): + self.node(""" + import assert from 'node:assert/strict'; + import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; + import { readRunContext } from './tests/integration/fixtures/run-ownership.ts'; + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'lightnvr-eng-test-')); + try { + fs.writeFileSync(path.join(dir,'sentinel'), 'keep'); + assert.throws(() => readRunContext({LIGHTNVR_TEST_DIR:dir,LIGHTNVR_RUN_TOKEN:'forged'})); + assert.equal(fs.readFileSync(path.join(dir,'sentinel'),'utf8'),'keep'); + } finally {fs.rmSync(dir,{recursive:true});} + """) + + def test_generated_config_is_owned_and_loopback(self): + self.node(""" + import assert from 'node:assert/strict'; import fs from 'node:fs'; + import { createRun } from './scripts/quality/run-integration.mjs'; + import { readRunContext } from './tests/integration/fixtures/run-ownership.ts'; + const run = createRun({task:'T13-test',binary:process.execPath,go2rtc:false}); + try { + const ctx = readRunContext(run.env); + const ini = fs.readFileSync(ctx.config,'utf8'); + assert.ok(ini.includes('bind_ip = 127.0.0.1')); + assert.ok(ini.includes('path = '+ctx.dir+'/lightnvr.db')); + assert.ok(!ini.includes('/tmp/lightnvr-test/')); + assert.equal(ctx.dir,run.dir); + fs.appendFileSync(ctx.config,'\\n# tampered'); + assert.throws(()=>readRunContext(run.env)); + } finally {fs.rmSync(run.dir,{recursive:true});fs.rmSync(run.artifacts,{recursive:true});} + """) + + def test_pid_provenance_rejects_unrelated_process(self): + self.node(""" + import assert from 'node:assert/strict'; import fs from 'node:fs'; + import { createRun } from './scripts/quality/run-integration.mjs'; + import { recordOwnedProcess } from './tests/integration/fixtures/run-ownership.ts'; + const run = createRun({task:'T13-pid-test',binary:process.execPath,go2rtc:false}); + try {assert.throws(()=>recordOwnedProcess(process.pid,run.env)); process.kill(process.pid,0);} + finally {fs.rmSync(run.dir,{recursive:true});fs.rmSync(run.artifacts,{recursive:true});} + """) + + def test_owned_process_stop_and_pid_reuse_rejection(self): + self.node(""" + import assert from 'node:assert/strict'; import fs from 'node:fs'; import path from 'node:path'; + import { spawn } from 'node:child_process'; import { once } from 'node:events'; + import { createRun } from './scripts/quality/run-integration.mjs'; + import { recordOwnedProcess, stopOwnedProcess } from './tests/integration/fixtures/run-ownership.ts'; + const run = createRun({task:'T13-own-test',binary:'/usr/bin/python3',go2rtc:false}); + const child = spawn('/usr/bin/python3',['-c','import time; time.sleep(30)','-c',run.config]); + try { + await once(child,'spawn'); recordOwnedProcess(child.pid,run.env); + const filename=path.join(run.dir,'process.json'); + const saved=JSON.parse(fs.readFileSync(filename,'utf8')); + fs.writeFileSync(filename,JSON.stringify({...saved,start:'wrong-start-time'})); + await assert.rejects(stopOwnedProcess(run.env),/identity changed/); process.kill(child.pid,0); + fs.writeFileSync(filename,JSON.stringify(saved)); + const exited=once(child,'exit'); await stopOwnedProcess(run.env); await exited; + assert.equal(JSON.parse(fs.readFileSync(path.join(run.artifacts,'shutdown.json'),'utf8')).graceful,true); + } finally {child.kill();fs.rmSync(run.dir,{recursive:true});fs.rmSync(run.artifacts,{recursive:true});} + """) + + def test_forced_shutdown_is_failure_and_descendants_are_cleaned(self): + self.node(""" + import assert from 'node:assert/strict'; import fs from 'node:fs'; import path from 'node:path'; + import { spawn } from 'node:child_process'; import { once } from 'node:events'; + import { createRun } from './scripts/quality/run-integration.mjs'; + import { recordOwnedProcess, stopOwnedProcess } from './tests/integration/fixtures/run-ownership.ts'; + const run=createRun({task:'T13-force-test',binary:'/usr/bin/python3',go2rtc:false}); + const child=spawn('/usr/bin/python3',['-c',"import subprocess,time,signal; signal.signal(signal.SIGTERM,signal.SIG_IGN); p=subprocess.Popen(['/usr/bin/sleep','30']); print(p.pid,flush=True); time.sleep(30)",'-c',run.config]); + try { + await once(child,'spawn'); await once(child.stdout,'data'); recordOwnedProcess(child.pid,run.env); + await assert.rejects(stopOwnedProcess(run.env,10),/graceful shutdown deadline/); + const report=JSON.parse(fs.readFileSync(path.join(run.artifacts,'shutdown.json'),'utf8')); + assert.equal(report.graceful,false); assert.equal(report.forcedKill,true); + assert.ok(report.observedChildren.length>0); assert.deepEqual(report.lingeringChildren,[]); + } finally {child.kill('SIGKILL');fs.rmSync(run.dir,{recursive:true});fs.rmSync(run.artifacts,{recursive:true});} + """) + + def test_cli_preserves_project_file_shard_and_grep(self): + self.node(""" + import assert from 'node:assert/strict'; + import { parseOptions, playwrightArgs } from './scripts/quality/run-integration.mjs'; + const options=parseOptions(['--task','T15.1','--project','all','--shard','2/3','--grep','@api|@ui','--file','tests/integration/specs/system-health.api.spec.ts','--file','tests/integration/specs/system-health.ui.spec.ts','--list']); + const args=playwrightArgs(options); + assert.equal(options.list,true);assert.equal(options.files.length,2); + assert.ok(!args.includes('--project'));assert.ok(args.includes('--shard=2/3')); + assert.equal(args[args.indexOf('--grep')+1],'@api|@ui'); + const filters=args.filter(arg=>arg.startsWith('^'));assert.equal(filters.length,2); + assert.ok(new RegExp(filters[0]).test(options.files[0])); + assert.ok(!new RegExp(filters[0]).test(options.files[0].replace('.api.','XapiY'))); + assert.ok(playwrightArgs(parseOptions(['--task=T15.1','--project=ui','--shard=1/2'])).includes('--project=ui')); + """) + + def test_cli_rejects_config_and_unsafe_file_or_shard(self): + self.node(""" + import assert from 'node:assert/strict'; + import { parseOptions } from './scripts/quality/run-integration.mjs'; + for (const extra of [['--config','/tmp/production'],['--file','package.json'],['--file','../outside.spec.ts'],['--shard','0/2'],['--shard','3/2'],['--shard','1/0'],['--project','unknown'],['--config-profile','production'],['--workers','8']]) assert.throws(()=>parseOptions(['--task','T15.1',...extra])); + assert.throws(()=>parseOptions(['--task','T15.1','--config-profile','health']),/binary/); + """) + + def test_health_profile_keeps_mqtt_and_omits_go2rtc(self): + self.node(""" + import assert from 'node:assert/strict';import fs from 'node:fs'; + import { parseOptions, createRun } from './scripts/quality/run-integration.mjs'; + const options=parseOptions(['--task','T15-health-test','--config-profile','health','--binary',process.execPath]); + const run=createRun(options); + try { + const ini=fs.readFileSync(run.config,'utf8'); + assert.ok(ini.includes('[mqtt]\\nenabled = true')); + assert.ok(ini.includes('broker_host = 127.0.0.1')); + assert.ok(ini.includes('broker_port = 18884')); + assert.ok(ini.includes('[go2rtc]\\nenabled = false')); + assert.equal(run.env.LIGHTNVR_SKIP_GO2RTC,'1'); + }finally{fs.rmSync(run.dir,{recursive:true});fs.rmSync(run.artifacts,{recursive:true});} + """) + + def test_discovery_does_not_need_binary_or_runtime_directory(self): + self.node(""" + import assert from 'node:assert/strict';import fs from 'node:fs'; + import { main } from './scripts/quality/run-integration.mjs'; + const before=fs.readdirSync('/tmp').filter(n=>n.startsWith('lightnvr-eng-T15-discovery-test-')); + assert.equal(await main(['--task','T15-discovery-test','--project','api','--grep','GET /api/system','--binary','/no-binary-required-for-discovery','--list']),0); + assert.deepEqual(fs.readdirSync('/tmp').filter(n=>n.startsWith('lightnvr-eng-T15-discovery-test-')),before); + fs.rmSync('build/eng-artifacts/T15-discovery-test',{recursive:true}); + """) + + def test_log_archive_ignores_external_symlinks_and_unrelated_data(self): + self.node(""" + import assert from 'node:assert/strict';import fs from 'node:fs';import path from 'node:path'; + import { createRun, archiveRunLogs } from './scripts/quality/run-integration.mjs'; + const run=createRun({task:'T15-archive-test',binary:process.execPath,go2rtc:false}); + try { + fs.writeFileSync(path.join(run.dir,'lightnvr.log'),'owned log'); + fs.writeFileSync(path.join(run.dir,'unrelated-secret'),'do not archive'); + fs.symlinkSync('/etc/passwd',path.join(run.dir,'other.log')); + archiveRunLogs(run); + const files=fs.readdirSync(path.join(run.artifacts,'runtime')); + assert.ok(files.includes('lightnvr.log'));assert.ok(files.includes('lightnvr.ini')); + assert.ok(!files.includes('other.log'));assert.ok(!files.includes('unrelated-secret')); + assert.equal(fs.readFileSync(path.join(run.artifacts,'runtime/lightnvr.log'),'utf8'),'owned log'); + }finally{fs.rmSync(run.dir,{recursive:true});fs.rmSync(run.artifacts,{recursive:true});} + """) + + def test_unselected_cli_fails_before_server_creation(self): + result = subprocess.run(['node','scripts/quality/run-integration.mjs','--task','../escape'], cwd=ROOT, capture_output=True) + self.assertNotEqual(result.returncode,0) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/quality/test_scanner_ci.py b/tests/quality/test_scanner_ci.py new file mode 100644 index 000000000..30052e43c --- /dev/null +++ b/tests/quality/test_scanner_ci.py @@ -0,0 +1,390 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +"""Real subprocess provenance and explicitly partial findings-lane checks.""" +import hashlib +import json +from pathlib import Path +import shutil +import subprocess +import sys +import tempfile +import unittest +import zipfile + +REPO = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(REPO / 'scripts/quality')) +import scanner_run +import findings_lane +import findings + +REVISION = 'a' * 40 + +class ScannerPipelineTests(unittest.TestCase): + def setUp(self): + self.root = Path(tempfile.mkdtemp(prefix='scanner-ci-')) + self.reports = self.root / 'reports' + (self.root / 'web/js').mkdir(parents=True) + (self.root / 'web/js/example.js').write_text('export const answer = 42;\n') + self.scanners = {'scanners': [ + {'id':'eslint-web','tool':'eslint','report_format':'eslint-json','access_status':'accessible','report_path_convention':'eslint.json'}, + {'id':'codeql-cpp','tool':'CodeQL','report_format':'sarif-2.1.0','access_status':'accessible','report_path_convention':'codeql.sarif'}]} + self.inventory = {'findings':[],'buckets':[]} + self.policy = {'dispositions':{'needs-triage':{'exception':False}},'accepted_gaps':[],'accepted_exit_codes':{'default':[0],'eslint-web':[0,1]},'require_run_manifest':True} + + def tearDown(self): + shutil.rmtree(self.root) + + def spec(self, messages=None, code=0): + report=[{'filePath':str(self.root/'web/js/example.js'),'messages':messages or []}] + return {'scanner_id':'eslint-web','version_command':[sys.executable,'-c','print("eslint 9.38.0")'], + 'commands':[{'argv':[sys.executable,'-c',f'import json,sys; print(json.dumps({report!r})); sys.exit({code})'], + 'stdout_report':'eslint.json','reports':['eslint.json'],'accepted_exit_codes':[0,1]}], + 'scope':{'kind':'eslint-json','report':'eslint.json'}} + + def run_scan(self, spec=None): + return scanner_run.run_scanner(spec or self.spec(),root=self.root,reports=self.reports,revision=REVISION) + + def lane(self, expected_revision=REVISION): + return findings_lane.evaluate_lane(reports=self.reports,inventory=self.inventory,scanners=self.scanners,policy=self.policy, + scanner_ids=['eslint-web'],expected_revision=expected_revision,today='2026-11-01') + + def manifest(self): + return json.loads((self.reports/'run-manifest.json').read_text()) + + def write_manifest(self, value): + (self.reports/'run-manifest.json').write_text(json.dumps(value)) + + def test_trivy_json_version_requires_and_records_actual_database_metadata(self): + spec=self.spec() + spec['version_format']='trivy-json' + metadata={'Version':'1.2.3','VulnerabilityDB':{'Version':2,'UpdatedAt':'2026-10-31T00:00:00Z'}} + spec['version_command']=[sys.executable,'-c',f'import json; print(json.dumps({metadata!r}))'] + entry=self.run_scan(spec) + self.assertEqual(entry.get('db_version'),metadata['VulnerabilityDB']) + self.assertEqual(entry['tool_version'],'1.2.3') + + def test_cppcheck_scope_uses_actual_progress_not_candidate_list(self): + spec=self.spec() + spec['scanner_id']='cppcheck' + spec['commands']=[{'argv':[sys.executable,'-c', + 'import pathlib; print("Checking web/js/example.js ..."); pathlib.Path("reports/cppcheck.xml").write_text("")'], + 'reports':['cppcheck.xml']}] + spec['scope']={'kind':'cppcheck-progress','report':'logs/cppcheck-0.stdout.log'} + entry=self.run_scan(spec) + self.assertEqual(entry['status'],'success',entry) + self.assertEqual(entry['scope']['files'],['web/js/example.js']) + self.assertIn('logs/cppcheck-0.stdout.log',entry['scope']['evidence_sha256']) + + def test_merged_diagnostic_stream_preserves_stdout_and_stderr(self): + spec=self.spec() + spec['commands'][0]['argv']=[sys.executable,'-c','import sys; print("out"); print("err",file=sys.stderr)'] + spec['commands'][0]['stderr_to_stdout']=True + spec['scope']={'kind':'invocation-files','files':['web/js/example.js']} + spec['commands'][0]['append_scope_files']=True + entry=self.run_scan(spec) + self.assertEqual(entry['status'],'success',entry) + self.assertEqual(set((self.reports/'eslint.json').read_text().splitlines()),{'out','err'}) + + def test_clean_subprocess_records_actual_files_versions_hashes_and_partial_pass(self): + result=self.run_scan() + self.assertEqual(result['status'],'success',result) + entry=self.manifest()['scanners']['eslint-web'] + self.assertEqual(entry['scope']['files'],['web/js/example.js']) + self.assertEqual(entry['files_scanned'],1) + self.assertEqual(entry['tool_version'],'eslint 9.38.0') + self.assertEqual(entry['report_sha256']['eslint.json'],hashlib.sha256((self.reports/'eslint.json').read_bytes()).hexdigest()) + verdict=self.lane() + self.assertEqual(verdict['status'],'partial-pass',verdict) + self.assertIsNone(verdict['global_verdict']) + self.assertEqual(verdict['omitted_scanners'],['codeql-cpp']) + + def test_new_finding_fails_even_when_scanner_exit_is_accepted(self): + self.run_scan(self.spec([{'ruleId':'jsx-a11y/alt-text','severity':2,'line':1,'column':1,'message':'Image needs alternate text.'}],1)) + verdict=self.lane() + self.assertEqual(verdict['status'],'partial-fail') + self.assertTrue(verdict['result']['failures']) + + def test_crashed_command_with_report_cannot_pass(self): + entry=self.run_scan(self.spec(code=139)) + self.assertEqual(entry['status'],'failure') + self.assertEqual(entry['exit_code'],139) + self.assertEqual(self.lane()['status'],'partial-fail') + + def test_timeout_is_recorded_and_cannot_pass(self): + spec=self.spec();spec['commands'][0]['argv']=[sys.executable,'-c','import time; time.sleep(10)'];spec['commands'][0]['timeout_seconds']=0.05 + entry=self.run_scan(spec) + self.assertEqual(entry['status'],'failure') + self.assertTrue(entry['commands'][0]['timed_out']) + + def test_stale_preexisting_report_is_rejected_without_overwrite(self): + self.reports.mkdir();(self.reports/'eslint.json').write_text('old') + with self.assertRaises(ValueError):self.run_scan() + self.assertEqual((self.reports/'eslint.json').read_text(),'old') + + def test_report_hash_tampering_fails(self): + self.run_scan();(self.reports/'eslint.json').write_text('[]') + self.assertEqual(self.lane()['status'],'partial-fail') + self.assertTrue(any('hash' in f['detail'] for f in self.lane()['result']['failures'])) + + def test_wrong_revision_and_missing_expectation_fail(self): + self.run_scan() + self.assertEqual(self.lane('b'*40)['status'],'partial-fail') + with self.assertRaises(ValueError):self.lane(None) + + def test_required_manifest_metadata_fields_fail_closed(self): + self.run_scan();original=self.manifest() + for field in ['status','exit_code','tool_version','reports','report_sha256','scope','files_scanned']: + with self.subTest(field=field): + changed=json.loads(json.dumps(original));del changed['scanners']['eslint-web'][field];self.write_manifest(changed) + self.assertEqual(self.lane()['status'],'partial-fail') + self.write_manifest(original) + + def test_candidate_inventory_is_not_scan_evidence(self): + spec=self.spec();spec['scope']={'kind':'candidate-files','files':['web/js/example.js']} + entry=self.run_scan(spec) + self.assertEqual(entry['status'],'failure') + self.assertEqual(self.lane()['status'],'partial-fail') + + def test_report_paths_and_source_symlinks_cannot_escape_roots(self): + for name in ['../outside.json','/tmp/outside.json']: + with self.subTest(name=name): + spec=self.spec();spec['commands'][0]['reports']=[name] + with self.assertRaises(ValueError):self.run_scan(spec) + outside=self.root.parent/(self.root.name+'-outside.js');outside.write_text('private') + try: + (self.root/'web/js/example.js').unlink();(self.root/'web/js/example.js').symlink_to(outside) + entry=self.run_scan();self.assertEqual(entry['status'],'failure') + self.assertNotIn('private',json.dumps(entry)) + finally:outside.unlink() + + def test_report_symlink_is_never_followed_for_hashing(self): + self.run_scan();p=self.reports/'eslint.json';p.unlink();p.symlink_to(self.root/'web/js/example.js') + self.assertEqual(self.lane()['status'],'partial-fail') + + def test_global_gate_still_requires_omitted_scanner(self): + self.run_scan() + result=findings.evaluate(self.reports,self.inventory,self.scanners,self.policy,'2026-11-01',expected_revision=REVISION) + self.assertEqual(result['status'],'fail') + self.assertTrue(any(f['scanner']=='codeql-cpp' for f in result['failures'])) + + def test_unknown_or_empty_lane_cannot_drop_scope(self): + for ids in [[],['unknown']]: + with self.assertRaises(ValueError):findings_lane.evaluate_lane(reports=self.reports,inventory=self.inventory,scanners=self.scanners,policy=self.policy,scanner_ids=ids,expected_revision=REVISION,today='2026-11-01') + + def test_proposed_exception_is_not_approved_by_lane_projection(self): + self.run_scan() + self.policy['accepted_gaps']=[{'scanner':'eslint-web','condition':'not-scanned','owner':'maintainers','reason':'proposed','expires':'2027-01-01'}] + self.assertEqual(self.lane()['status'],'partial-fail') + + def test_lane_cli_announces_partial_and_does_not_write_baseline(self): + self.run_scan() + for name,data in [('inventory',self.inventory),('scanners',self.scanners),('policy',self.policy)]: (self.root/(name+'.json')).write_text(json.dumps(data)) + before=(self.root/'inventory.json').read_bytes() + process=subprocess.run([sys.executable,str(REPO/'scripts/quality/findings_lane.py'),'--reports',str(self.reports),'--inventory',str(self.root/'inventory.json'),'--scanners',str(self.root/'scanners.json'),'--policy',str(self.root/'policy.json'),'--scanner','eslint-web','--expected-revision',REVISION],capture_output=True,text=True) + self.assertEqual(process.returncode,0,process.stdout+process.stderr) + self.assertIn('PARTIAL',process.stdout) + self.assertIn('codeql-cpp',process.stdout) + self.assertNotIn('RESULT: PASS',process.stdout) + self.assertEqual((self.root/'inventory.json').read_bytes(),before) + + def test_missing_tool_version_cannot_pass(self): + spec=self.spec();spec['version_command']=[sys.executable,'-c','pass'] + entry=self.run_scan(spec) + self.assertEqual(entry['status'],'failure') + self.assertEqual(self.lane()['status'],'partial-fail') + + def test_codeql_scope_comes_from_actual_archive_and_matches_checkout(self): + archive=self.root/'src.zip' + with zipfile.ZipFile(archive,'w') as zipped: + zipped.write(self.root/'web/js/example.js',str(self.root/'web/js/example.js').lstrip('/')) + zipped.writestr('usr/include/external.h','external') + spec=self.spec();spec['scope']={'kind':'codeql-source-archive','archive':'src.zip'} + entry=self.run_scan(spec) + self.assertEqual(entry['status'],'success',entry) + self.assertEqual(entry['scope']['files'],['web/js/example.js']) + self.assertEqual(entry['scope']['external_entries_omitted'],1) + (self.root/'web/js/example.js').write_text('changed source') + with self.assertRaises(ValueError):scanner_run.scope_evidence(spec,self.root,self.reports) + + def test_second_command_failure_is_not_hidden_by_first_success(self): + spec=self.spec();second=json.loads(json.dumps(spec['commands'][0])) + second.update(argv=[sys.executable,'-c','print("[]"); raise SystemExit(5)'],stdout_report='second.json',reports=['second.json']) + spec['commands'].append(second) + entry=self.run_scan(spec) + self.assertEqual(entry['status'],'failure') + self.assertEqual(entry['exit_code'],5) + self.assertEqual(self.lane()['status'],'partial-fail') + + def test_malformed_scope_hashes_fail_without_checker_crash(self): + self.run_scan();manifest=self.manifest() + manifest['scanners']['eslint-web']['scope']['evidence_sha256']=42 + self.write_manifest(manifest) + self.assertEqual(self.lane()['status'],'partial-fail') + + def test_existing_scanner_manifest_is_preserved(self): + self.run_scan();before=(self.reports/'run-manifest.json').read_bytes() + with self.assertRaises(ValueError):self.run_scan() + self.assertEqual((self.reports/'run-manifest.json').read_bytes(),before) + + def test_undeclared_or_stale_file_list_is_not_tool_evidence(self): + self.reports.mkdir();(self.reports/'old-scope.json').write_text('{"files":["web/js/example.js"]}') + spec=self.spec();spec['scope']={'kind':'file-list-report','report':'old-scope.json'} + with self.assertRaises(ValueError):self.run_scan(spec) + spec['commands'][0]['evidence_outputs']=['old-scope.json'] + with self.assertRaises(ValueError):self.run_scan(spec) + + def test_policy_pin_cannot_bypass_required_report_hashes(self): + self.run_scan() + shutil.copyfile(self.reports/'eslint.json',self.reports/'unhashed.json') + self.policy['report_pins']=[{'reports_dir':str(self.reports),'scanner':'eslint-web','paths':['unhashed.json']}] + self.assertEqual(self.lane()['status'],'partial-fail') + + +class FaacCompiledProvenanceTests(unittest.TestCase): + """Synthetic scanner output tests validation, not real scanner qualification.""" + def setUp(self): + self.tmp = tempfile.TemporaryDirectory(prefix='faac-scan-') + self.addCleanup(self.tmp.cleanup) + self.root = Path(self.tmp.name) + self.reports = self.root / 'reports' + self.build = self.root / 'build/native' + self.copy = self.build / '_deps/lightnvr-faac' + self.patchdir = self.root / 'patches/faac' + self.patchdir.mkdir(parents=True) + self.patch = self.patchdir / '0001-bound-input-pointer-arithmetic.patch' + self.patch.write_text('synthetic fixture patch\n') + self.source = self.root / 'third_party/faac' + names = ['libfaac/frame.c', 'libfaac/faac.c', 'libfaac/faac_internal.h'] + inputs = {}; outputs = {}; rows = [] + for name in names: + original = self.source / name; original.parent.mkdir(parents=True, exist_ok=True) + original.write_text('original ' + name + '\n') + copied = self.copy / name; copied.parent.mkdir(parents=True, exist_ok=True) + copied.write_text('patched ' + name + '\n') + inputs[name] = scanner_run.sha(original); outputs[name] = scanner_run.sha(copied) + rows.append({'upstream_path':name, 'input_sha256':inputs[name], + 'output_sha256':outputs[name], 'compiled_path':str(copied)}) + (self.copy / 'include').mkdir() + self.units = sorted(str(self.copy / p) for p in names if p.endswith('.c')) + subprocess.run(['git','-C',str(self.source),'init','-q'],check=True) + subprocess.run(['git','-C',str(self.source),'add','.'],check=True) + subprocess.run(['git','-C',str(self.source),'-c','user.name=Fixture', + '-c','user.email=fixture@example.invalid','commit','-qm','fixture'],check=True) + self.pin=subprocess.check_output(['git','-C',str(self.source),'rev-parse','HEAD'],text=True).strip() + subprocess.run(['git','-C',str(self.root),'init','-q'],check=True) + subprocess.run(['git','-C',str(self.root),'update-index','--add','--cacheinfo', + '160000,'+self.pin+',third_party/faac'],check=True) + self.static = {'schema_version':1, 'upstream_commit':self.pin, + 'patch_sha256':scanner_run.sha(self.patch), 'input_files':inputs, + 'output_files':outputs, 'translation_units':sorted(p for p in names if p.endswith('.c'))} + (self.patchdir / 'manifest.json').write_text(json.dumps(self.static)) + (self.patchdir / 'manifest.cmake').write_text('# fixture table\n') + self.prov = {'schema_version':1, 'upstream_commit':self.pin, + 'patch_sha256':self.static['patch_sha256'], + 'manifest_sha256':scanner_run.sha(self.patchdir / 'manifest.json'), + 'source_root':str(self.source), 'build_root':str(self.copy), + 'compiler':'synthetic cc', 'build_profile':'test', + 'files':rows, 'translation_units':self.units} + self.provpath = self.copy / 'provenance.json'; self.save() + self.commands = [{'directory':str(self.build), 'file':p, + 'arguments':['cc','-I'+str(self.copy/'include'),'-I'+str(self.copy/'libfaac'),'-c',p]} + for p in self.units] + self.compdb = self.build / 'compile_commands.json'; self.save_commands() + + def save(self): self.provpath.write_text(json.dumps(self.prov)) + def save_commands(self): self.compdb.write_text(json.dumps(self.commands)) + def rel(self, p): return Path(p).relative_to(self.root).as_posix() + def spec(self, kind='invocation-files', files=None): + files = files if files is not None else [self.rel(p) for p in self.units] + sid = 'codeql-cpp' if kind == 'codeql-source-archive' else 'clang-tidy' + scope = {'kind':kind, 'files':files} + command = {'argv':[sys.executable,'-c','print("[]")'], + 'reports':['scan.json'], 'stdout_report':'scan.json'} + if kind == 'invocation-files': command['append_scope_files'] = True + else: + archive = self.build / 'src.zip' + with zipfile.ZipFile(archive,'w') as z: + for f in files: z.write(self.root / f, f) + scope = {'kind':kind, 'archive':self.rel(archive)} + return {'scanner_id':sid, 'version_command':[sys.executable,'-c','print("synthetic scanner")'], + 'commands':[command], 'scope':scope, + 'faac':{'provenance':self.rel(self.provpath),'compile_commands':self.rel(self.compdb)}} + def run_scan(self, spec=None): + return scanner_run.run_scanner(spec or self.spec(), root=self.root, + reports=self.reports, revision=REVISION) + def fails(self, spec=None): + result = self.run_scan(spec); self.assertEqual(result['status'],'failure',result) + self.assertTrue(result['errors']) + + def test_valid_mapping_retains_actual_paths_and_archives_inputs(self): + result=self.run_scan(); self.assertEqual(result['status'],'success',result) + mapping=result['scope']['faac_provenance'] + self.assertEqual(mapping['upstream_commit'],self.pin) + self.assertEqual(result['scope']['files'],[self.rel(p) for p in self.units]) + self.assertEqual(len(mapping['files']),3) + for path,digest in mapping['evidence_sha256'].items(): + self.assertEqual(scanner_run.sha(self.reports/path),digest) + + def test_missing_required_faac_spec_cannot_skip_validation(self): + spec=self.spec(); del spec['faac']; self.fails(spec) + def test_missing_provenance_refused(self): + self.provpath.unlink(); self.fails() + def test_changed_generated_source_refused(self): + Path(self.units[0]).write_text('tampered\n'); self.fails() + def test_changed_original_source_refused(self): + (self.source/'libfaac/frame.c').write_text('tampered\n'); self.fails() + def test_stale_pin_refused(self): + self.prov['upstream_commit']='d'*40; self.save(); self.fails() + def test_changed_patch_refused(self): + self.patch.write_text('another patch\n'); self.fails() + def test_mixed_original_compile_root_refused(self): + self.commands[0]['file']=str(self.source/'libfaac/faac.c'); self.save_commands(); self.fails() + def test_original_private_include_root_refused(self): + self.commands[0]['arguments'].insert(1,'-I'+str(self.source/'libfaac')) + self.save_commands(); self.fails() + def test_unlisted_translation_unit_refused(self): + self.prov['translation_units']=self.units[:1]; self.save(); self.fails() + def test_omitted_actual_invocation_cannot_pass(self): + self.fails(self.spec(files=[self.rel(self.units[0])])) + def test_codeql_requires_generated_private_header_and_units(self): + self.fails(self.spec('codeql-source-archive')) + def test_codeql_archive_preserves_generated_paths(self): + names=[self.rel(self.copy/p) for p in self.static['input_files']] + result=self.run_scan(self.spec('codeql-source-archive',names)) + self.assertEqual(result['status'],'success',result) + self.assertEqual(set(result['scope']['files']),set(names)) + self.assertEqual(len(result['scope']['faac_provenance']['files']),3) + def test_codeql_changed_archive_source_fails(self): + names=[self.rel(self.copy/p) for p in self.static['input_files']] + spec=self.spec('codeql-source-archive',names) + with zipfile.ZipFile(self.build/'src.zip','w') as z: + for name in names:z.writestr(name,b'not compiled source') + self.fails(spec) + def test_actual_cppcheck_progress_must_include_all_units(self): + spec=self.spec(); spec['scanner_id']='cppcheck' + spec['scope']={'kind':'cppcheck-progress','report':'logs/cppcheck-0.stdout.log'} + spec['commands']=[{'argv':[sys.executable,'-c', + 'import pathlib; print('+repr('Checking '+self.rel(self.units[0])+' ...')+'); pathlib.Path("reports/scan.json").write_text("[]")'], + 'reports':['scan.json']}] + self.fails(spec) + def test_native_selection_preserves_prior_sources_and_adds_compiled_faac(self): + for path in ['src/example.c','src/sod/excluded.c','external/tool/src/example.c']: + file=self.root/path;file.parent.mkdir(parents=True,exist_ok=True);file.write_text('int example;') + self.commands.append({'directory':str(self.root),'file':str(file),'arguments':['cc','-c',str(file)]}) + self.save_commands() + options={'provenance':self.rel(self.provpath),'compile_commands':self.rel(self.compdb)} + clang=scanner_run.native_analysis_inputs(self.root,options,'clang-tidy') + cpp=scanner_run.native_analysis_inputs(self.root,options,'cppcheck') + self.assertEqual(set(clang),{'src/example.c'}|{self.rel(x) for x in self.units}) + self.assertEqual(set(cpp),{'src/example.c','src/sod/excluded.c','external/tool/src/example.c'}|{self.rel(x) for x in self.units}) + + def test_actual_gitlink_drift_refused(self): + subprocess.run(['git','-C',str(self.root),'update-index','--add','--cacheinfo', + '160000,'+'f'*40+',third_party/faac'],check=True) + self.fails() + + def test_filtered_compile_database_cannot_invent_flags(self): + filtered=self.build/'analysis.json' + changed=json.loads(json.dumps(self.commands));changed[0]['arguments'].append('-DCHANGED') + filtered.write_text(json.dumps(changed)) + spec=self.spec();spec['faac']['analysis_compile_commands']=self.rel(filtered) + self.fails(spec) diff --git a/tests/tsconfig.json b/tests/tsconfig.json new file mode 100644 index 000000000..b4d12af68 --- /dev/null +++ b/tests/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true, + "strict": true, + "noUncheckedIndexedAccess": true, + "isolatedModules": true, + "noEmit": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "types": ["node"], + "allowJs": true, + "checkJs": false + }, + "include": ["integration/**/*.ts", "load/**/*.ts", "../playwright.config.ts"], + "exclude": ["../node_modules", "../web/node_modules", "../build"] +} diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 7bcee145f..dbf6fedba 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -439,4 +439,15 @@ foreach(SYSTEM_HEALTH_TEST_FRAGMENT IN LISTS SYSTEM_HEALTH_TEST_FRAGMENTS) include("${SYSTEM_HEALTH_TEST_FRAGMENT}") endforeach() +# Engineering tasks own independent fragments; stable order keeps registration deterministic. +if(CMAKE_VERSION VERSION_LESS "3.12") + file(GLOB ENGINEERING_TEST_FRAGMENTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake/eng/*.cmake") +else() + file(GLOB ENGINEERING_TEST_FRAGMENTS CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/cmake/eng/*.cmake") +endif() +list(SORT ENGINEERING_TEST_FRAGMENTS) +foreach(ENGINEERING_TEST_FRAGMENT IN LISTS ENGINEERING_TEST_FRAGMENTS) + include("${ENGINEERING_TEST_FRAGMENT}") +endforeach() + message(STATUS "Building Unity unit tests (Layer 1 + Layer 2 + Layer 3)") diff --git a/tests/unit/cmake/eng/delete_stream_launch_failure.cmake b/tests/unit/cmake/eng/delete_stream_launch_failure.cmake new file mode 100644 index 000000000..01a31eb26 --- /dev/null +++ b/tests/unit/cmake/eng/delete_stream_launch_failure.cmake @@ -0,0 +1,4 @@ +add_layer3_test(test_delete_stream_launch_failure) +target_link_options(test_delete_stream_launch_failure PRIVATE + "-Wl,--wrap=pthread_create" "-Wl,--wrap=free") +set_tests_properties(test_delete_stream_launch_failure PROPERTIES TIMEOUT 15) diff --git a/tests/unit/cmake/eng/incomplete_media_validation.cmake b/tests/unit/cmake/eng/incomplete_media_validation.cmake new file mode 100644 index 000000000..f81622e0d --- /dev/null +++ b/tests/unit/cmake/eng/incomplete_media_validation.cmake @@ -0,0 +1,11 @@ +add_executable(test_incomplete_media_validation + ${CMAKE_SOURCE_DIR}/tests/operational/lifecycle/test_incomplete_media_validation.c) +target_include_directories(test_incomplete_media_validation PRIVATE + ${CMAKE_SOURCE_DIR}/include ${FFMPEG_INCLUDE_DIRS}) +target_link_libraries(test_incomplete_media_validation PRIVATE unity_lib lightnvr_lib + ${FFMPEG_LIBRARIES} ${SQLITE_LIBRARIES} ${CURL_LIBRARIES} ${SSL_LIBRARIES} + ${HTTP_BACKEND_LIBS} inih_lib pthread dl m) +add_test(NAME test_incomplete_media_validation COMMAND test_incomplete_media_validation) +set_tests_properties(test_incomplete_media_validation PROPERTIES TIMEOUT 20) +target_link_options(test_incomplete_media_validation PRIVATE + "-Wl,--wrap=stream_has_continuous_writer" "-Wl,--wrap=mp4_recording_thread_alive") diff --git a/tests/unit/cmake/eng/lifecycle.cmake b/tests/unit/cmake/eng/lifecycle.cmake new file mode 100644 index 000000000..9a184f011 --- /dev/null +++ b/tests/unit/cmake/eng/lifecycle.cmake @@ -0,0 +1 @@ +set_tests_properties(test_shutdown_coordinator PROPERTIES TIMEOUT 15) diff --git a/tests/unit/cmake/eng/media.cmake b/tests/unit/cmake/eng/media.cmake new file mode 100644 index 000000000..029fa3856 --- /dev/null +++ b/tests/unit/cmake/eng/media.cmake @@ -0,0 +1,58 @@ +# T27 preserves all existing media tests and declares bounded native deadlines. +set_tests_properties(test_mp4_audio_recovery test_mp4_recovery_watchdog + test_mp4_writer_watchdog test_mp4_segment_boundaries test_mp4_segment_timestamps + test_mp4_faststart PROPERTIES TIMEOUT 30 LABELS "engineering;media") + +add_executable(test_mp4_path_collision ${CMAKE_SOURCE_DIR}/tests/operational/media/test_mp4_path_collision.c) +target_include_directories(test_mp4_path_collision PRIVATE ${CMAKE_SOURCE_DIR}/include ${FFMPEG_INCLUDE_DIRS}) +target_link_libraries(test_mp4_path_collision PRIVATE unity_lib lightnvr_lib ${FFMPEG_LIBRARIES} + ${SQLITE_LIBRARIES} ${CURL_LIBRARIES} ${SSL_LIBRARIES} ${HTTP_BACKEND_LIBS} inih_lib pthread dl m) +if(CJSON_BUNDLED) + target_link_libraries(test_mp4_path_collision PRIVATE cjson_lib) +elseif(CJSON_FOUND) + target_link_libraries(test_mp4_path_collision PRIVATE ${CJSON_LIBRARIES}) +endif() +if(ENABLE_MQTT AND MOSQUITTO_FOUND) + target_link_libraries(test_mp4_path_collision PRIVATE ${MOSQUITTO_LIBRARIES}) +endif() +if(ENABLE_SOD) + target_link_libraries(test_mp4_path_collision PRIVATE sod) +endif() +set_target_properties(test_mp4_path_collision PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") +add_test(NAME test_mp4_path_collision COMMAND test_mp4_path_collision) +set_tests_properties(test_mp4_path_collision PROPERTIES TIMEOUT 20 LABELS "engineering;media") +target_link_options(test_mp4_path_collision PRIVATE "-Wl,--wrap=time" "-Wl,--wrap=open" + "-Wl,--wrap=record_segment" "-Wl,--wrap=get_stream_config_by_name" + "-Wl,--wrap=storage_should_pause_recording" "-Wl,--wrap=av_usleep" + "-Wl,--wrap=prepare_placed_mp4_recording_path" + "-Wl,--wrap=add_recording_metadata" "-Wl,--wrap=update_recording_metadata") + +# Preserve existing deterministic loop seams; reservation itself is exercised +# with real files/threads by test_mp4_path_collision above. +target_link_options(test_mp4_forced_reconnect PRIVATE "-Wl,--wrap=reserve_mp4_recording_path" + "-Wl,--wrap=release_mp4_recording_path") + +add_executable(test_mp4_pressure_reconnect ${CMAKE_SOURCE_DIR}/tests/operational/media/test_mp4_pressure_reconnect.c) +target_include_directories(test_mp4_pressure_reconnect PRIVATE ${CMAKE_SOURCE_DIR}/include ${FFMPEG_INCLUDE_DIRS}) +target_link_libraries(test_mp4_pressure_reconnect PRIVATE unity_lib lightnvr_lib ${FFMPEG_LIBRARIES} + ${SQLITE_LIBRARIES} ${CURL_LIBRARIES} ${SSL_LIBRARIES} ${HTTP_BACKEND_LIBS} inih_lib pthread dl m) +if(CJSON_BUNDLED) + target_link_libraries(test_mp4_pressure_reconnect PRIVATE cjson_lib) +elseif(CJSON_FOUND) + target_link_libraries(test_mp4_pressure_reconnect PRIVATE ${CJSON_LIBRARIES}) +endif() +if(ENABLE_MQTT AND MOSQUITTO_FOUND) + target_link_libraries(test_mp4_pressure_reconnect PRIVATE ${MOSQUITTO_LIBRARIES}) +endif() +if(ENABLE_SOD) + target_link_libraries(test_mp4_pressure_reconnect PRIVATE sod) +endif() +set_target_properties(test_mp4_pressure_reconnect PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") +add_test(NAME test_mp4_pressure_reconnect COMMAND test_mp4_pressure_reconnect) +set_tests_properties(test_mp4_pressure_reconnect PROPERTIES TIMEOUT 20 LABELS "engineering;media") +target_link_options(test_mp4_pressure_reconnect PRIVATE + "-Wl,--wrap=record_segment" "-Wl,--wrap=get_stream_config_by_name" + "-Wl,--wrap=storage_should_pause_recording" "-Wl,--wrap=trigger_storage_cleanup" + "-Wl,--wrap=av_usleep" "-Wl,--wrap=prepare_placed_mp4_recording_path" + "-Wl,--wrap=reserve_mp4_recording_path" "-Wl,--wrap=release_mp4_recording_path" + "-Wl,--wrap=avformat_close_input" "-Wl,--wrap=cleanup_audio_transcoder") diff --git a/tests/unit/cmake/eng/put_stream_launch_failure.cmake b/tests/unit/cmake/eng/put_stream_launch_failure.cmake new file mode 100644 index 000000000..6fe991c13 --- /dev/null +++ b/tests/unit/cmake/eng/put_stream_launch_failure.cmake @@ -0,0 +1,4 @@ +add_layer3_test(test_put_stream_launch_failure) +target_link_options(test_put_stream_launch_failure PRIVATE + "-Wl,--wrap=pthread_create" "-Wl,--wrap=free") +set_tests_properties(test_put_stream_launch_failure PROPERTIES TIMEOUT 15) diff --git a/tests/unit/cmake/eng/stale_recording_prune.cmake b/tests/unit/cmake/eng/stale_recording_prune.cmake new file mode 100644 index 000000000..184a0bcee --- /dev/null +++ b/tests/unit/cmake/eng/stale_recording_prune.cmake @@ -0,0 +1,13 @@ +add_executable(test_stale_recording_prune + ${CMAKE_SOURCE_DIR}/tests/operational/lifecycle/test_stale_recording_prune.c) +target_include_directories(test_stale_recording_prune PRIVATE + ${CMAKE_SOURCE_DIR}/include ${FFMPEG_INCLUDE_DIRS}) +target_link_libraries(test_stale_recording_prune PRIVATE unity_lib lightnvr_lib + ${FFMPEG_LIBRARIES} ${SQLITE_LIBRARIES} ${CURL_LIBRARIES} ${SSL_LIBRARIES} + ${HTTP_BACKEND_LIBS} inih_lib pthread dl m) +target_link_options(test_stale_recording_prune PRIVATE + "-Wl,--wrap=stream_has_continuous_writer" "-Wl,--wrap=mp4_recording_thread_alive" + "-Wl,--wrap=get_stale_incomplete_recordings_page" + "-Wl,--wrap=get_recording_metadata_by_id") +add_test(NAME test_stale_recording_prune COMMAND test_stale_recording_prune) +set_tests_properties(test_stale_recording_prune PROPERTIES TIMEOUT 30) diff --git a/tests/unit/cmake/eng/storage.cmake b/tests/unit/cmake/eng/storage.cmake new file mode 100644 index 000000000..7767d3bc6 --- /dev/null +++ b/tests/unit/cmake/eng/storage.cmake @@ -0,0 +1,31 @@ +# T28 test-only storage seams. GNU ld wrappers affect these executables only. +target_link_options(test_storage_manager_retention PRIVATE "-Wl,--wrap=unlink") +set_tests_properties(test_storage_manager_retention test_storage_retention_sqlite PROPERTIES TIMEOUT 30) + +# Use the existing Layer 3 link contract for operational sources outside unit/. +foreach(storage_test IN ITEMS test_storage_recording_faults test_storage_writer_backpressure) + add_executable(${storage_test} ${CMAKE_SOURCE_DIR}/tests/operational/storage/${storage_test}.c) + target_include_directories(${storage_test} PRIVATE ${CMAKE_SOURCE_DIR}/include ${FFMPEG_INCLUDE_DIRS}) + target_link_libraries(${storage_test} PRIVATE unity_lib lightnvr_lib ${FFMPEG_LIBRARIES} + ${SQLITE_LIBRARIES} ${CURL_LIBRARIES} ${SSL_LIBRARIES} ${HTTP_BACKEND_LIBS} inih_lib pthread dl m) + if(CJSON_BUNDLED) + target_link_libraries(${storage_test} PRIVATE cjson_lib) + elseif(CJSON_FOUND) + target_link_libraries(${storage_test} PRIVATE ${CJSON_LIBRARIES}) + endif() + if(ENABLE_MQTT AND MOSQUITTO_FOUND) + target_link_libraries(${storage_test} PRIVATE ${MOSQUITTO_LIBRARIES}) + endif() + if(ENABLE_SOD) + target_link_libraries(${storage_test} PRIVATE sod) + endif() + set_target_properties(${storage_test} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + add_test(NAME ${storage_test} COMMAND ${storage_test}) + set_tests_properties(${storage_test} PROPERTIES TIMEOUT 20) +endforeach() +target_link_options(test_storage_recording_faults PRIVATE + "-Wl,--wrap=av_interleaved_write_frame" "-Wl,--wrap=avio_open" "-Wl,--wrap=avio_closep") +target_link_options(test_storage_writer_backpressure PRIVATE + "-Wl,--wrap=record_segment" "-Wl,--wrap=av_usleep" + "-Wl,--wrap=get_stream_config_by_name" "-Wl,--wrap=storage_should_pause_recording" + "-Wl,--wrap=prepare_placed_mp4_recording_path" "-Wl,--wrap=trigger_storage_cleanup") diff --git a/tests/unit/cmake/eng/stream_state_disposal.cmake b/tests/unit/cmake/eng/stream_state_disposal.cmake new file mode 100644 index 000000000..6770d3f04 --- /dev/null +++ b/tests/unit/cmake/eng/stream_state_disposal.cmake @@ -0,0 +1,8 @@ +add_layer3_test(test_stream_state_disposal) +target_link_options(test_stream_state_disposal PRIVATE + "-Wl,--wrap=pthread_mutex_destroy" + "-Wl,--wrap=stream_stop_hls" + "-Wl,--wrap=stop_mp4_recording" + "-Wl,--wrap=reset_timestamp_tracker" + "-Wl,--wrap=remove_timestamp_tracker") +set_tests_properties(test_stream_state_disposal PROPERTIES TIMEOUT 15) diff --git a/tests/unit/cmake/eng/t7_11.cmake b/tests/unit/cmake/eng/t7_11.cmake new file mode 100644 index 000000000..8b45b682f --- /dev/null +++ b/tests/unit/cmake/eng/t7_11.cmake @@ -0,0 +1,2 @@ +add_layer2_test(test_t7_11) +set_tests_properties(test_t7_11 PROPERTIES TIMEOUT 30 LABELS "engineering;security;characterization") diff --git a/tests/unit/cmake/eng/t7_16.cmake b/tests/unit/cmake/eng/t7_16.cmake new file mode 100644 index 000000000..53076051e --- /dev/null +++ b/tests/unit/cmake/eng/t7_16.cmake @@ -0,0 +1,11 @@ +add_layer3_test(test_t7_16) +target_link_options(test_t7_16 PRIVATE + -Wl,--wrap=avcodec_open2 + -Wl,--wrap=faac_adapter_open + -Wl,--wrap=faac_adapter_close + -Wl,--wrap=faac_adapter_get_sample_rate + -Wl,--wrap=faac_adapter_get_frame_samples + -Wl,--wrap=faac_adapter_get_encoder_delay + -Wl,--wrap=faac_adapter_get_object_type + -Wl,--wrap=faac_adapter_get_asc) +set_tests_properties(test_t7_16 PROPERTIES TIMEOUT 30 LABELS "engineering;media") diff --git a/tests/unit/cmake/eng/t7_23.cmake b/tests/unit/cmake/eng/t7_23.cmake new file mode 100644 index 000000000..f9fbabae3 --- /dev/null +++ b/tests/unit/cmake/eng/t7_23.cmake @@ -0,0 +1,2 @@ +add_layer2_test(test_t7_23) +set_tests_properties(test_t7_23 PROPERTIES TIMEOUT 20 LABELS "engineering;security") diff --git a/tests/unit/cmake/eng/t7_24.cmake b/tests/unit/cmake/eng/t7_24.cmake new file mode 100644 index 000000000..7664965f1 --- /dev/null +++ b/tests/unit/cmake/eng/t7_24.cmake @@ -0,0 +1,2 @@ +add_layer3_test(test_t7_24) +set_tests_properties(test_t7_24 PROPERTIES TIMEOUT 60 LABELS "engineering;media") diff --git a/tests/unit/cmake/eng/t7_25.cmake b/tests/unit/cmake/eng/t7_25.cmake new file mode 100644 index 000000000..972562dc1 --- /dev/null +++ b/tests/unit/cmake/eng/t7_25.cmake @@ -0,0 +1,6 @@ +add_layer2_test(test_t7_25) +target_link_options(test_t7_25 PRIVATE -Wl,--wrap=lstat -Wl,--wrap=fstatat + -Wl,--wrap=openat -Wl,--wrap=close -Wl,--wrap=fstat -Wl,--wrap=mkdirat + -Wl,--wrap=syscall -Wl,--wrap=unlinkat + -Wl,--wrap=readdir -Wl,--wrap=lightnvr_uuid_generate_v4) +set_tests_properties(test_t7_25 PROPERTIES TIMEOUT 30 LABELS "engineering;security") diff --git a/tests/unit/cmake/eng/t7_25_retention.cmake b/tests/unit/cmake/eng/t7_25_retention.cmake new file mode 100644 index 000000000..2f6193ae0 --- /dev/null +++ b/tests/unit/cmake/eng/t7_25_retention.cmake @@ -0,0 +1,6 @@ +add_layer3_test(test_t7_25_retention) +target_link_options(test_t7_25_retention PRIVATE -Wl,--wrap=stat -Wl,--wrap=fstat + -Wl,--wrap=openat -Wl,--wrap=close -Wl,--wrap=opendir -Wl,--wrap=closedir + -Wl,--wrap=readdir -Wl,--wrap=realloc -Wl,--wrap=strdup + -Wl,--wrap=syscall -Wl,--wrap=unlinkat) +set_tests_properties(test_t7_25_retention PROPERTIES TIMEOUT 30 LABELS "engineering;security") diff --git a/tests/unit/cmake/eng/t7_26.cmake b/tests/unit/cmake/eng/t7_26.cmake new file mode 100644 index 000000000..86a218239 --- /dev/null +++ b/tests/unit/cmake/eng/t7_26.cmake @@ -0,0 +1,3 @@ +add_layer2_test(test_t7_26) +target_link_options(test_t7_26 PRIVATE -Wl,--wrap=lstat -Wl,--wrap=syscall -Wl,--wrap=unlink -Wl,--wrap=db_storage_migration_update_progress -Wl,--wrap=db_storage_migration_commit_location) +set_tests_properties(test_t7_26 PROPERTIES TIMEOUT 30 LABELS "engineering;security") diff --git a/tests/unit/cmake/eng/t7_27.cmake b/tests/unit/cmake/eng/t7_27.cmake new file mode 100644 index 000000000..abb7321c2 --- /dev/null +++ b/tests/unit/cmake/eng/t7_27.cmake @@ -0,0 +1,3 @@ +add_layer2_test(test_t7_27) +target_link_options(test_t7_27 PRIVATE -Wl,--wrap=mkstemp -Wl,--wrap=openat -Wl,--wrap=fstat -Wl,--wrap=syscall -Wl,--wrap=mkdtemp -Wl,--wrap=access -Wl,--wrap=rename -Wl,--wrap=db_operator_floor_plan_set_background) +set_tests_properties(test_t7_27 PROPERTIES TIMEOUT 30 LABELS "engineering;security") diff --git a/tests/unit/cmake/eng/t7_28.cmake b/tests/unit/cmake/eng/t7_28.cmake new file mode 100644 index 000000000..aedb650e1 --- /dev/null +++ b/tests/unit/cmake/eng/t7_28.cmake @@ -0,0 +1,8 @@ +add_layer2_test(test_t7_28) +target_link_options(test_t7_28 PRIVATE + -Wl,--wrap=db_event_route_count -Wl,--wrap=db_event_route_list + -Wl,--wrap=db_event_route_generation -Wl,--wrap=db_event_destination_generation + -Wl,--wrap=db_event_route_suppression_prune + -Wl,--wrap=fopen -Wl,--wrap=stat + -Wl,--wrap=calloc -Wl,--wrap=malloc -Wl,--wrap=free) +set_tests_properties(test_t7_28 PROPERTIES TIMEOUT 30 LABELS "engineering;security") diff --git a/tests/unit/cmake/eng/t7_2_buffers.cmake b/tests/unit/cmake/eng/t7_2_buffers.cmake new file mode 100644 index 000000000..f2a02e762 --- /dev/null +++ b/tests/unit/cmake/eng/t7_2_buffers.cmake @@ -0,0 +1,9 @@ +if(ENABLE_GO2RTC) + add_layer3_test(test_t7_2_buffers) + target_link_options(test_t7_2_buffers PRIVATE + -Wl,--wrap=get_db_handle + -Wl,--wrap=malloc + -Wl,--wrap=curl_easy_perform + -Wl,--wrap=curl_easy_getinfo) + set_tests_properties(test_t7_2_buffers PROPERTIES TIMEOUT 30 LABELS "engineering;security") +endif() diff --git a/tests/unit/cmake/eng/t7_2_files.cmake b/tests/unit/cmake/eng/t7_2_files.cmake new file mode 100644 index 000000000..930feaba6 --- /dev/null +++ b/tests/unit/cmake/eng/t7_2_files.cmake @@ -0,0 +1,20 @@ +include(CheckCCompilerFlag) +check_c_compiler_flag("-ftrivial-auto-var-init=zero" T7_2_FILES_HAS_ZERO_INIT) +if(NOT T7_2_FILES_HAS_ZERO_INIT) + message(FATAL_ERROR "T7.2.3 deterministic descriptor probe requires compiler auto-variable initialization") +endif() +# Compile the unchanged helper separately for the permitted-value probe only. +# Do not alter lightnvr_lib's source properties or production compiler flags. +add_library(t7_2_files_path_probe OBJECT ${CMAKE_SOURCE_DIR}/src/core/path_utils.c) +target_include_directories(t7_2_files_path_probe PRIVATE ${CMAKE_SOURCE_DIR}/include) +target_compile_options(t7_2_files_path_probe PRIVATE -ftrivial-auto-var-init=zero) +add_layer3_test(test_t7_2_files) +target_sources(test_t7_2_files PRIVATE $) +target_link_options(test_t7_2_files PRIVATE + -Wl,--wrap=open -Wl,--wrap=fchmod -Wl,--wrap=fstatat + -Wl,--wrap=get_db_handle -Wl,--wrap=get_streaming_config + -Wl,--wrap=pthread_create -Wl,--wrap=storage_migration_worker_start + -Wl,--wrap=storage_source_worker_start -Wl,--wrap=storage_target_health_register + -Wl,--wrap=get_all_stream_names_including_disabled + -Wl,--wrap=get_orphaned_recording_stream_names) +set_tests_properties(test_t7_2_files PROPERTIES TIMEOUT 30 LABELS "engineering;security") diff --git a/tests/unit/cmake/eng/t7_2_migrations.cmake b/tests/unit/cmake/eng/t7_2_migrations.cmake new file mode 100644 index 000000000..aa59f093d --- /dev/null +++ b/tests/unit/cmake/eng/t7_2_migrations.cmake @@ -0,0 +1,7 @@ +add_executable(test_t7_2_migrations ${CMAKE_SOURCE_DIR}/tests/unit/test_t7_2_migrations.c) +target_include_directories(test_t7_2_migrations PRIVATE ${CMAKE_SOURCE_DIR}/include ${FFMPEG_INCLUDE_DIRS}) +target_link_libraries(test_t7_2_migrations PRIVATE unity_lib lightnvr_lib + ${FFMPEG_LIBRARIES} ${SQLITE_LIBRARIES} ${CURL_LIBRARIES} ${SSL_LIBRARIES} + ${HTTP_BACKEND_LIBS} inih_lib pthread dl m) +add_test(NAME test_t7_2_migrations COMMAND test_t7_2_migrations) +set_tests_properties(test_t7_2_migrations PROPERTIES TIMEOUT 15 LABELS "engineering;security") diff --git a/tests/unit/cmake/eng/t7_2_settings.cmake b/tests/unit/cmake/eng/t7_2_settings.cmake new file mode 100644 index 000000000..bbf8d7f24 --- /dev/null +++ b/tests/unit/cmake/eng/t7_2_settings.cmake @@ -0,0 +1,4 @@ +add_layer3_test(test_t7_2_settings) +target_link_options(test_t7_2_settings PRIVATE + -Wl,--wrap=save_config -Wl,--wrap=system -Wl,--wrap=popen) +set_tests_properties(test_t7_2_settings PROPERTIES TIMEOUT 30 LABELS "engineering;security") diff --git a/tests/unit/cmake/eng/t7_53.cmake b/tests/unit/cmake/eng/t7_53.cmake new file mode 100644 index 000000000..cc37b9bda --- /dev/null +++ b/tests/unit/cmake/eng/t7_53.cmake @@ -0,0 +1,8 @@ +add_layer2_test(test_t7_53) +target_link_options(test_t7_53 PRIVATE + -Wl,--wrap=realloc + -Wl,--wrap=db_authorization_load_user_grants + -Wl,--wrap=camera_collection_filter_load_for_authorization + -Wl,--wrap=camera_collection_filter_matches + -Wl,--wrap=camera_collection_filter_free) +set_tests_properties(test_t7_53 PROPERTIES TIMEOUT 30 LABELS "engineering;security") diff --git a/tests/unit/cmake/eng/t7_54.cmake b/tests/unit/cmake/eng/t7_54.cmake new file mode 100644 index 000000000..6b1a35779 --- /dev/null +++ b/tests/unit/cmake/eng/t7_54.cmake @@ -0,0 +1,4 @@ +add_layer2_test(test_t7_54) +target_link_options(test_t7_54 PRIVATE + -Wl,--wrap=malloc -Wl,--wrap=strdup -Wl,--wrap=ftell -Wl,--wrap=fread) +set_tests_properties(test_t7_54 PROPERTIES TIMEOUT 30 LABELS "engineering;security") diff --git a/tests/unit/cmake/eng/t7_55.cmake b/tests/unit/cmake/eng/t7_55.cmake new file mode 100644 index 000000000..6d0caec9c --- /dev/null +++ b/tests/unit/cmake/eng/t7_55.cmake @@ -0,0 +1,4 @@ +add_layer3_test(test_t7_55) +target_link_options(test_t7_55 PRIVATE + -Wl,--wrap=fopen -Wl,--wrap=fgets -Wl,--wrap=fclose) +set_tests_properties(test_t7_55 PROPERTIES TIMEOUT 30 LABELS "engineering;security") diff --git a/tests/unit/cmake/eng/t7_58.cmake b/tests/unit/cmake/eng/t7_58.cmake new file mode 100644 index 000000000..fe6daae32 --- /dev/null +++ b/tests/unit/cmake/eng/t7_58.cmake @@ -0,0 +1,3 @@ +add_layer3_test(test_t7_58) +target_link_options(test_t7_58 PRIVATE -Wl,--wrap=fopen -Wl,--wrap=fclose) +set_tests_properties(test_t7_58 PROPERTIES TIMEOUT 30 LABELS "engineering;security") diff --git a/tests/unit/cmake/eng/t7_59.cmake b/tests/unit/cmake/eng/t7_59.cmake new file mode 100644 index 000000000..a5e34167e --- /dev/null +++ b/tests/unit/cmake/eng/t7_59.cmake @@ -0,0 +1,5 @@ +add_layer3_test(test_t7_59) +target_link_options(test_t7_59 PRIVATE + -Wl,--wrap=clock_gettime -Wl,--wrap=pthread_create + -Wl,--wrap=pthread_join -Wl,--wrap=pthread_cond_timedwait) +set_tests_properties(test_t7_59 PROPERTIES TIMEOUT 30 LABELS "engineering;security") diff --git a/tests/unit/cmake/eng/t7_59_start_failure.cmake b/tests/unit/cmake/eng/t7_59_start_failure.cmake new file mode 100644 index 000000000..97b11c83c --- /dev/null +++ b/tests/unit/cmake/eng/t7_59_start_failure.cmake @@ -0,0 +1,4 @@ +add_layer3_test(test_t7_59_start_failure) +target_link_options(test_t7_59_start_failure PRIVATE + -Wl,--wrap=pthread_cond_wait -Wl,--wrap=pthread_mutex_lock -Wl,--wrap=pthread_create -Wl,--wrap=pthread_join) +set_tests_properties(test_t7_59_start_failure PROPERTIES TIMEOUT 30 LABELS "engineering;security") diff --git a/tests/unit/test_authorization.c b/tests/unit/test_authorization.c index d0268d311..a28499c77 100644 --- a/tests/unit/test_authorization.c +++ b/tests/unit/test_authorization.c @@ -54,6 +54,9 @@ #define OPERATOR_ROLE_UUID "00000000-0000-4000-8000-000000000002" #define ADMIN_ROLE_UUID "00000000-0000-4000-8000-000000000001" +static char scoped_storage_root[128]; +static char saved_storage_path[MAX_PATH_LENGTH]; + static stream_config_t create_camera(const char *name, const char *tags) { stream_config_t stream; memset(&stream, 0, sizeof(stream)); @@ -199,10 +202,12 @@ static cJSON *call_handler_path( req.num_headers = 1; } handler(&req, &res); - TEST_ASSERT_EQUAL_INT(expected_status, res.status_code); - cJSON *json = res.body ? cJSON_Parse((const char *)res.body) : NULL; - TEST_ASSERT_NOT_NULL(json); + int actual_status = res.status_code; + cJSON *json = actual_status == expected_status && res.body + ? cJSON_Parse((const char *)res.body) : NULL; http_response_free(&res); + TEST_ASSERT_EQUAL_INT(expected_status, actual_status); + TEST_ASSERT_NOT_NULL(json); return json; } @@ -257,6 +262,9 @@ static int authorize_stream_with_key(const char *api_key, void setUp(void) { sqlite3 *db = get_db_handle(); + safe_strcpy(saved_storage_path, g_config.storage_path, + sizeof(saved_storage_path), 0); + scoped_storage_root[0] = '\0'; g_config.web_auth_enabled = false; g_config.demo_mode = false; sqlite3_exec( @@ -278,6 +286,13 @@ void setUp(void) { void tearDown(void) { g_config.web_auth_enabled = false; g_config.demo_mode = false; + safe_strcpy(g_config.storage_path, saved_storage_path, + sizeof(g_config.storage_path), 0); + if (scoped_storage_root[0]) { + int removed = rmdir(scoped_storage_root); + scoped_storage_root[0] = '\0'; + TEST_ASSERT_EQUAL_INT(0, removed); + } } void test_action_catalog_is_stable_and_complete(void) { @@ -1303,6 +1318,15 @@ void test_users_api_reports_authorization_mode(void) { } void test_sensitive_handlers_enforce_camera_scoped_policy(void) { + char root_template[] = "/tmp/lightnvr-authz-storage-XXXXXX"; + char *created_root = mkdtemp(root_template); + TEST_ASSERT_NOT_NULL(created_root); + safe_strcpy(scoped_storage_root, created_root, + sizeof(scoped_storage_root), 0); + safe_strcpy(g_config.storage_path, scoped_storage_root, + sizeof(g_config.storage_path), 0); + /* An available legacy root makes missing files authoritative for deletion. + * Keep files absent: download assertions intentionally require 404/409. */ stream_config_t allowed = create_camera("Scoped Camera", "Outdoor"); stream_config_t denied = create_camera("Other Camera", "Indoor"); allowed.ptz_enabled = true; @@ -1339,8 +1363,8 @@ void test_sensitive_handlers_enforce_camera_scoped_policy(void) { memset(&allowed_recording, 0, sizeof(allowed_recording)); safe_strcpy(allowed_recording.stream_name, allowed.name, sizeof(allowed_recording.stream_name), 0); - safe_strcpy(allowed_recording.file_path, "/tmp/lightnvr-scoped-recording.mp4", - sizeof(allowed_recording.file_path), 0); + snprintf(allowed_recording.file_path, sizeof(allowed_recording.file_path), + "%s/scoped-recording.mp4", scoped_storage_root); allowed_recording.start_time = 100; allowed_recording.end_time = 200; allowed_recording.is_complete = true; @@ -1351,8 +1375,8 @@ void test_sensitive_handlers_enforce_camera_scoped_policy(void) { recording_metadata_t denied_recording = allowed_recording; safe_strcpy(denied_recording.stream_name, denied.name, sizeof(denied_recording.stream_name), 0); - safe_strcpy(denied_recording.file_path, "/tmp/lightnvr-denied-recording.mp4", - sizeof(denied_recording.file_path), 0); + snprintf(denied_recording.file_path, sizeof(denied_recording.file_path), + "%s/denied-recording.mp4", scoped_storage_root); uint64_t denied_id = add_recording_metadata(&denied_recording); TEST_ASSERT_NOT_EQUAL(0, denied_id); @@ -1497,9 +1521,8 @@ void test_sensitive_handlers_enforce_camera_scoped_policy(void) { 0, get_recording_metadata_by_id(allowed_id, &reloaded)); recording_metadata_t batch_recording = allowed_recording; - safe_strcpy(batch_recording.file_path, - "/tmp/lightnvr-scoped-batch-recording.mp4", - sizeof(batch_recording.file_path), 0); + snprintf(batch_recording.file_path, sizeof(batch_recording.file_path), + "%s/scoped-batch-recording.mp4", scoped_storage_root); uint64_t batch_id = add_recording_metadata(&batch_recording); TEST_ASSERT_NOT_EQUAL(0, batch_id); snprintf(batch_body, sizeof(batch_body), "{\"ids\":[%llu]}", diff --git a/tests/unit/test_delete_stream_launch_failure.c b/tests/unit/test_delete_stream_launch_failure.c new file mode 100644 index 000000000..c7c6fd034 --- /dev/null +++ b/tests/unit/test_delete_stream_launch_failure.c @@ -0,0 +1,131 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include +#include "unity.h" +#include "core/config.h" +#include "core/logger.h" +#include "database/db_core.h" +#include "database/db_streams.h" +#include "video/stream_manager.h" +#include "video/stream_state.h" +#include "web/api_handlers.h" +#include "web/request_response.h" + +static bool inject_failure; +static unsigned launch_attempts, task_frees; +static void *failed_task; +static stream_handle_t original_stream; +static stream_config_t original_config, original_persisted; +static http_request_t request; +static http_response_t response; + +int __real_pthread_create(pthread_t *, const pthread_attr_t *, void *(*)(void *), void *); +int __wrap_pthread_create(pthread_t *thread, const pthread_attr_t *attr, + void *(*callback)(void *), void *argument) { + if (!inject_failure) return __real_pthread_create(thread, attr, callback, argument); + launch_attempts++; + failed_task = argument; + return EAGAIN; /* POSIX failure: callback is never scheduled. */ +} +void __real_free(void *); +void __wrap_free(void *allocation) { + if (allocation && allocation == failed_task) { + task_frees++; + failed_task = NULL; + } + __real_free(allocation); +} + +void setUp(void) { + inject_failure = false; + launch_attempts = task_frees = 0; + failed_task = NULL; + g_config.web_auth_enabled = false; + TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_exec(get_db_handle(), "DELETE FROM streams", NULL, NULL, NULL)); + TEST_ASSERT_EQUAL_INT(0, init_stream_state_manager(4)); + TEST_ASSERT_EQUAL_INT(0, init_stream_manager(4)); + stream_config_t config = {0}; + strcpy(config.name, "launch_failure_camera"); + strcpy(config.url, "rtsp://127.0.0.1:1/unopened"); + strcpy(config.codec, "h264"); + config.enabled = true; + config.width = 640; + config.height = 480; + config.fps = 10; + TEST_ASSERT_GREATER_THAN(0, add_stream_config(&config)); + memset(&original_persisted, 0, sizeof(original_persisted)); + TEST_ASSERT_EQUAL_INT(0, get_stream_config_by_name(config.name, &original_persisted)); + original_stream = add_stream(&config); + TEST_ASSERT_NOT_NULL(original_stream); + TEST_ASSERT_EQUAL_INT(0, get_stream_config(original_stream, &original_config)); + http_request_init(&request); + http_response_init(&response); + strcpy(request.path, "/api/streams/launch_failure_camera"); + strcpy(request.method_str, "DELETE"); +} +void tearDown(void) { + inject_failure = false; + http_response_free(&response); + shutdown_stream_manager(); + shutdown_stream_state_manager(); + g_config.web_auth_enabled = false; +} +static void assert_unchanged(void) { + TEST_ASSERT_EQUAL_PTR(original_stream, get_stream_by_name(original_config.name)); + stream_config_t runtime = {0}, persisted = {0}; + TEST_ASSERT_EQUAL_INT(0, get_stream_config(original_stream, &runtime)); + TEST_ASSERT_EQUAL_MEMORY(&original_config, &runtime, sizeof(runtime)); + TEST_ASSERT_EQUAL_INT(0, get_stream_config_by_name(original_config.name, &persisted)); + TEST_ASSERT_EQUAL_MEMORY(&original_persisted, &persisted, sizeof(persisted)); + TEST_ASSERT_EQUAL_INT(1, get_total_stream_count()); +} +static void assert_failed_launch(bool permanent) { + strcpy(request.query_string, permanent ? "permanent=true" : ""); + inject_failure = true; + handle_delete_stream(&request, &response); + inject_failure = false; + TEST_ASSERT_EQUAL_UINT(1, launch_attempts); + TEST_ASSERT_EQUAL_UINT(1, task_frees); + TEST_ASSERT_NULL(failed_task); + assert_unchanged(); + TEST_ASSERT_EQUAL_INT(500, response.status_code); + cJSON *body = cJSON_Parse((const char *)response.body); + TEST_ASSERT_NOT_NULL(body); + cJSON *error = cJSON_GetObjectItemCaseSensitive(body, "error"); + TEST_ASSERT_TRUE(cJSON_IsString(error)); + TEST_ASSERT_EQUAL_STRING("Failed to start delete task", error->valuestring); + TEST_ASSERT_NULL(cJSON_GetObjectItemCaseSensitive(body, "success")); + cJSON_Delete(body); +} +void test_soft_delete_launch_failure(void) { assert_failed_launch(false); } +void test_permanent_delete_launch_failure(void) { assert_failed_launch(true); } +void test_unauthorized_delete_never_launches_worker(void) { + g_config.web_auth_enabled = true; + inject_failure = true; + handle_delete_stream(&request, &response); + inject_failure = false; + TEST_ASSERT_EQUAL_INT(401, response.status_code); + TEST_ASSERT_EQUAL_UINT(0, launch_attempts); + TEST_ASSERT_EQUAL_UINT(0, task_frees); + assert_unchanged(); +} +int main(void) { + char path[160]; + snprintf(path, sizeof(path), "/tmp/lightnvr-delete-launch-%ld.db", (long)getpid()); + init_logger(); + load_default_config(&g_config); + if (init_database_ex(path, DB_INIT_NO_CHECK | DB_INIT_NO_BACKUP) != 0) return 1; + UNITY_BEGIN(); + RUN_TEST(test_soft_delete_launch_failure); + RUN_TEST(test_permanent_delete_launch_failure); + RUN_TEST(test_unauthorized_delete_never_launches_worker); + int result = UNITY_END(); + shutdown_database(); + unlink(path); + return result; +} diff --git a/tests/unit/test_mp4_forced_reconnect.c b/tests/unit/test_mp4_forced_reconnect.c index 57bab4c7b..17eb62568 100644 --- a/tests/unit/test_mp4_forced_reconnect.c +++ b/tests/unit/test_mp4_forced_reconnect.c @@ -3,6 +3,14 @@ #include "../../src/video/mp4_writer_thread.c" #include "unity.h" +/* The segment itself is already a deterministic seam in this loop test. */ +int __wrap_reserve_mp4_recording_path(char *path,size_t size,storage_placement_t *placement) { + (void)path;(void)size;(void)placement;return 123; +} +int __wrap_release_mp4_recording_path(int descriptor,const char *path) { + (void)descriptor;(void)path;return 0; +} + static mp4_writer_t writer; static mp4_writer_thread_t reader; static AVFormatContext *audio_input; diff --git a/tests/unit/test_mp4_recovery_watchdog.c b/tests/unit/test_mp4_recovery_watchdog.c index b529ffef0..cd94bbdee 100644 --- a/tests/unit/test_mp4_recovery_watchdog.c +++ b/tests/unit/test_mp4_recovery_watchdog.c @@ -11,9 +11,12 @@ static const int *health_script; static size_t script_length, checks; static int starts, stops, responses, response_dead_seconds; static bool fail_restart; +static unsigned sleeps, stop_after_sleeps; unsigned __wrap_sleep(unsigned seconds) { TEST_ASSERT_EQUAL_UINT(1, seconds); + sleeps++; + if (stop_after_sleeps && sleeps == stop_after_sleeps) ctx.running = 0; TEST_ASSERT_LESS_OR_EQUAL_UINT(script_length, checks); return 0; } @@ -84,6 +87,7 @@ void setUp(void) { starts = stops = responses = response_dead_seconds = 0; checks = 0; fail_restart = false; + sleeps = stop_after_sleeps = 0; } void tearDown(void) { @@ -137,11 +141,39 @@ static void test_failed_restart_cleans_up_writer_registration(void) { TEST_ASSERT_EQUAL_INT(0, responses); } +static void test_five_failed_restarts_wait_full_cooldown_then_retry(void) { + int script[31] = {0}; + script[30] = 1; + run_script(script, 31); + TEST_ASSERT_EQUAL_INT(7, starts); // initial + five retries + post-cooldown + TEST_ASSERT_EQUAL_INT(7, stops); + TEST_ASSERT_EQUAL_UINT(31 + 300, sleeps); +} + +static void test_shutdown_interrupts_cooldown_without_retry(void) { + int script[31] = {0}; + // Keep the outer loop alive after the final scripted health check until + // shutdown is requested from within cooldown's seventh one-second sleep. + health_script = script; + script_length = 31; + stop_after_sleeps = 30 + 7; + mp4_recording_thread(&ctx); + TEST_ASSERT_EQUAL_UINT(30, checks); + TEST_ASSERT_EQUAL_UINT(37, sleeps); + TEST_ASSERT_EQUAL_INT(6, starts); + TEST_ASSERT_EQUAL_INT(7, stops); // five restarts, cooldown stop, final cleanup + TEST_ASSERT_FALSE(ctx.running); + TEST_ASSERT_NULL(ctx.mp4_writer); + TEST_ASSERT_NULL(get_mp4_writer_for_stream(ctx.config.name)); +} + int main(void) { UNITY_BEGIN(); RUN_TEST(test_brief_inactivity_reports_responsive_without_restart); RUN_TEST(test_response_clears_dead_count_before_a_later_outage); RUN_TEST(test_sustained_outage_stops_before_restart_and_recovers); RUN_TEST(test_failed_restart_cleans_up_writer_registration); + RUN_TEST(test_five_failed_restarts_wait_full_cooldown_then_retry); + RUN_TEST(test_shutdown_interrupts_cooldown_without_retry); return UNITY_END(); } diff --git a/tests/unit/test_mp4_segment_boundaries.c b/tests/unit/test_mp4_segment_boundaries.c index 5b7a7ab17..385e54661 100644 --- a/tests/unit/test_mp4_segment_boundaries.c +++ b/tests/unit/test_mp4_segment_boundaries.c @@ -7,6 +7,7 @@ #include #include +#include #include "unity.h" #include "core/config.h" @@ -214,11 +215,76 @@ void test_non_keyframe_at_duration_waits_for_next_keyframe(void) { record_and_check_boundary(64, 32); } +/* The established contract retains exactly one shared boundary keyframe. + * Verify its byte identity and frame cadence across three real decoded files; + * overlap beyond that one frame or a missing frame is never accepted. */ +void test_three_segments_decode_with_one_shared_boundary_frame(void) { + open_fixture(1); + int counts[3] = {0}; + int decoded[3] = {0}; + int64_t boundaries[3] = {0}; + uint8_t first_hash[3][16], last_hash[3][16]; + for (int n = 0; n < 3; n++) { + TEST_ASSERT_EQUAL_INT(0, record_segment(source_path, output_path, 2, 0, + &input, &segment, NULL, NULL, NULL, NULL)); + TEST_ASSERT_NOT_NULL(segment.pending_video_keyframe); + boundaries[n] = segment.pending_video_keyframe->dts; + AVFormatContext *recorded = NULL; + AVPacket *packet = av_packet_alloc(); + AVFrame *frame = av_frame_alloc(); + TEST_ASSERT_EQUAL_INT(0, avformat_open_input(&recorded, output_path, NULL, NULL)); + TEST_ASSERT_GREATER_OR_EQUAL(0, avformat_find_stream_info(recorded, NULL)); + const AVCodec *codec = avcodec_find_decoder(recorded->streams[0]->codecpar->codec_id); + TEST_ASSERT_NOT_NULL(codec); + AVCodecContext *decoder = avcodec_alloc_context3(codec); + TEST_ASSERT_EQUAL_INT(0, avcodec_parameters_to_context(decoder, recorded->streams[0]->codecpar)); + TEST_ASSERT_EQUAL_INT(0, avcodec_open2(decoder, codec, NULL)); + int64_t previous = AV_NOPTS_VALUE; + while (av_read_frame(recorded, packet) >= 0) { + if (packet->stream_index == 0) { + TEST_ASSERT_NOT_EQUAL(AV_NOPTS_VALUE, packet->dts); + if (previous != AV_NOPTS_VALUE) { + int64_t expected_step = av_rescale_q(1, (AVRational){1,24}, recorded->streams[0]->time_base); + TEST_ASSERT_INT64_WITHIN(1, expected_step, packet->dts - previous); + } + if (counts[n] == 0) av_md5_sum(first_hash[n], packet->data, packet->size); + av_md5_sum(last_hash[n], packet->data, packet->size); + previous = packet->dts; + counts[n]++; + TEST_ASSERT_EQUAL_INT(0, avcodec_send_packet(decoder, packet)); + while (avcodec_receive_frame(decoder, frame) >= 0) { + decoded[n]++; + av_frame_unref(frame); + } + } + av_packet_unref(packet); + } + TEST_ASSERT_EQUAL_INT(0, avcodec_send_packet(decoder, NULL)); + while (avcodec_receive_frame(decoder, frame) >= 0) { + decoded[n]++; + av_frame_unref(frame); + } + avcodec_free_context(&decoder); + avformat_close_input(&recorded); + av_packet_free(&packet); + av_frame_free(&frame); + } + for (int n = 0; n < 3; n++) { + TEST_ASSERT_EQUAL_INT64(av_rescale_q(2 * (n + 1), (AVRational){1,1}, + input->streams[0]->time_base), boundaries[n]); + TEST_ASSERT_EQUAL_INT(counts[n], decoded[n]); + TEST_ASSERT_EQUAL_INT_MESSAGE(2 * 24 + 1, counts[n], + "Exactly one boundary frame may overlap consecutive segments"); + if (n > 0) TEST_ASSERT_EQUAL_MEMORY(last_hash[n-1], first_hash[n], 16); + } +} + int main(void) { UNITY_BEGIN(); RUN_TEST(test_keyframe_at_duration_closes_current_segment_and_starts_next); RUN_TEST(test_non_keyframe_at_duration_waits_for_next_keyframe); RUN_TEST(test_segments_snapshot_live_faststart_setting_at_open); RUN_TEST(test_writer_keeps_faststart_snapshot_after_live_update); + RUN_TEST(test_three_segments_decode_with_one_shared_boundary_frame); return UNITY_END(); } diff --git a/tests/unit/test_put_stream_launch_failure.c b/tests/unit/test_put_stream_launch_failure.c new file mode 100644 index 000000000..6938164f9 --- /dev/null +++ b/tests/unit/test_put_stream_launch_failure.c @@ -0,0 +1,131 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include +#include "unity.h" +#include "core/config.h" +#include "core/logger.h" +#include "database/db_core.h" +#include "database/db_streams.h" +#include "video/stream_manager.h" +#include "video/stream_state.h" +#include "web/api_handlers.h" +#include "web/request_response.h" + +static bool inject_failure; +static unsigned launch_attempts, task_frees; +static void *failed_task; +static stream_handle_t original_stream; +static stream_config_t original_config, original_persisted; +static http_request_t request; +static http_response_t response; + +int __real_pthread_create(pthread_t *, const pthread_attr_t *, void *(*)(void *), void *); +int __wrap_pthread_create(pthread_t *thread, const pthread_attr_t *attr, + void *(*callback)(void *), void *argument) { + if (!inject_failure) return __real_pthread_create(thread, attr, callback, argument); + launch_attempts++; + failed_task = argument; + return EAGAIN; /* POSIX failure: callback is never scheduled. */ +} +void __real_free(void *); +void __wrap_free(void *allocation) { + if (allocation && allocation == failed_task) { + task_frees++; + failed_task = NULL; + } + __real_free(allocation); +} + +void setUp(void) { + inject_failure = false; + launch_attempts = task_frees = 0; + failed_task = NULL; + g_config.web_auth_enabled = false; + TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_exec(get_db_handle(), "DELETE FROM streams", NULL, NULL, NULL)); + TEST_ASSERT_EQUAL_INT(0, init_stream_state_manager(4)); + TEST_ASSERT_EQUAL_INT(0, init_stream_manager(4)); + stream_config_t config = {0}; + strcpy(config.name, "put_launch_failure_camera"); + strcpy(config.url, "rtsp://127.0.0.1:1/unopened"); + strcpy(config.codec, "h264"); + config.enabled = true; + config.width = 640; + config.height = 480; + config.fps = 10; + TEST_ASSERT_GREATER_THAN(0, add_stream_config(&config)); + memset(&original_persisted, 0, sizeof(original_persisted)); + TEST_ASSERT_EQUAL_INT(0, get_stream_config_by_name(config.name, &original_persisted)); + original_stream = add_stream(&config); + TEST_ASSERT_NOT_NULL(original_stream); + TEST_ASSERT_EQUAL_INT(0, get_stream_config(original_stream, &original_config)); + http_request_init(&request); + http_response_init(&response); + strcpy(request.path, "/api/streams/put_launch_failure_camera"); + strcpy(request.method_str, "PUT"); +} +void tearDown(void) { + inject_failure = false; + http_response_free(&response); + shutdown_stream_manager(); + shutdown_stream_state_manager(); + g_config.web_auth_enabled = false; +} +static void assert_unchanged(void) { + TEST_ASSERT_EQUAL_PTR(original_stream, get_stream_by_name(original_config.name)); + stream_config_t runtime = {0}, persisted = {0}; + TEST_ASSERT_EQUAL_INT(0, get_stream_config(original_stream, &runtime)); + TEST_ASSERT_EQUAL_MEMORY(&original_config, &runtime, sizeof(runtime)); + TEST_ASSERT_EQUAL_INT(0, get_stream_config_by_name(original_config.name, &persisted)); + TEST_ASSERT_EQUAL_MEMORY(&original_persisted, &persisted, sizeof(persisted)); + TEST_ASSERT_EQUAL_INT(1, get_total_stream_count()); +} +static void assert_failed_launch(void) { + static const char body[] = "{\"enabled\":false,\"priority\":8}"; + request.body = (uint8_t *)body; + request.body_len = sizeof(body) - 1; + inject_failure = true; + handle_put_stream(&request, &response); + inject_failure = false; + TEST_ASSERT_EQUAL_UINT(1, launch_attempts); + TEST_ASSERT_EQUAL_UINT(1, task_frees); + TEST_ASSERT_NULL(failed_task); + assert_unchanged(); + TEST_ASSERT_EQUAL_INT(500, response.status_code); + cJSON *json = cJSON_Parse((const char *)response.body); + TEST_ASSERT_NOT_NULL(json); + cJSON *error = cJSON_GetObjectItemCaseSensitive(json, "error"); + TEST_ASSERT_TRUE(cJSON_IsString(error)); + TEST_ASSERT_EQUAL_STRING("Failed to start update task", error->valuestring); + TEST_ASSERT_NULL(cJSON_GetObjectItemCaseSensitive(json, "success")); + cJSON_Delete(json); +} +void test_update_launch_failure(void) { assert_failed_launch(); } +void test_unauthorized_update_never_launches_worker(void) { + g_config.web_auth_enabled = true; + inject_failure = true; + handle_put_stream(&request, &response); + inject_failure = false; + TEST_ASSERT_EQUAL_INT(401, response.status_code); + TEST_ASSERT_EQUAL_UINT(0, launch_attempts); + TEST_ASSERT_EQUAL_UINT(0, task_frees); + assert_unchanged(); +} +int main(void) { + char path[160]; + snprintf(path, sizeof(path), "/tmp/lightnvr-put-launch-%ld.db", (long)getpid()); + init_logger(); + load_default_config(&g_config); + if (init_database_ex(path, DB_INIT_NO_CHECK | DB_INIT_NO_BACKUP) != 0) return 1; + UNITY_BEGIN(); + RUN_TEST(test_update_launch_failure); + RUN_TEST(test_unauthorized_update_never_launches_worker); + int result = UNITY_END(); + shutdown_database(); + unlink(path); + return result; +} diff --git a/tests/unit/test_shutdown_coordinator.c b/tests/unit/test_shutdown_coordinator.c index af9753e00..7f6bf534a 100644 --- a/tests/unit/test_shutdown_coordinator.c +++ b/tests/unit/test_shutdown_coordinator.c @@ -12,6 +12,7 @@ #include #include +#include #include "unity.h" #include "core/shutdown_coordinator.h" #include "core/logger.h" @@ -128,7 +129,7 @@ void test_wait_blocks_until_timeout_when_component_registered(void) { struct timespec start, end; clock_gettime(CLOCK_MONOTONIC, &start); - wait_for_all_components_stopped(1); + bool stopped = wait_for_all_components_stopped(1); clock_gettime(CLOCK_MONOTONIC, &end); long elapsed_ms = (end.tv_sec - start.tv_sec) * 1000 + @@ -137,6 +138,8 @@ void test_wait_blocks_until_timeout_when_component_registered(void) { /* Should have actually waited for roughly the 1s timeout, not returned * instantly via the fast-path. */ TEST_ASSERT_GREATER_OR_EQUAL_INT(900, elapsed_ms); + TEST_ASSERT_LESS_THAN_INT(2500, elapsed_ms); + TEST_ASSERT_FALSE_MESSAGE(stopped, "A deadline is not graceful completion"); } void test_wait_all_stopped_after_marking_stopped(void) { @@ -147,13 +150,71 @@ void test_wait_all_stopped_after_marking_stopped(void) { } void test_wait_all_stopped_timeout_when_running(void) { - register_component("persistent", COMPONENT_OTHER, NULL, 1); - /* Component stays RUNNING → should timeout (1 second) */ - bool result = wait_for_all_components_stopped(1); - /* On a fast system it times out; result may be false */ - /* We don't assert a specific value — just that it returns */ - (void)result; - TEST_PASS(); + int id = register_component("persistent", COMPONENT_OTHER, NULL, 1); + bool result = wait_for_all_components_stopped(0); + TEST_ASSERT_FALSE_MESSAGE(result, "Timeout must remain distinguishable from stopped"); + TEST_ASSERT_EQUAL_INT_MESSAGE(COMPONENT_RUNNING, get_component_state(id), + "A timeout cannot assert that a live component exited"); +} + +static void *finish_component(void *argument) { + int id = *(int *)argument; + struct timespec delay = { .tv_nsec = 50000000 }; + nanosleep(&delay, NULL); + update_component_state(id, COMPONENT_STOPPED); + return NULL; +} + +void test_wait_observes_actual_component_completion(void) { + int id = register_component("finishes", COMPONENT_OTHER, NULL, 1); + pthread_t worker; + TEST_ASSERT_EQUAL_INT(0, pthread_create(&worker, NULL, finish_component, &id)); + bool stopped = wait_for_all_components_stopped(2); + TEST_ASSERT_EQUAL_INT(0, pthread_join(worker, NULL)); + TEST_ASSERT_TRUE(stopped); + TEST_ASSERT_EQUAL_INT(COMPONENT_STOPPED, get_component_state(id)); +} + +typedef struct { int id; atomic_bool finished; } spurious_context_t; +static void *wake_then_finish(void *argument) { + spurious_context_t *context = argument; + struct timespec delay = { .tv_nsec = 50000000 }; + nanosleep(&delay, NULL); + shutdown_coordinator_t *coordinator = get_shutdown_coordinator(); + pthread_mutex_lock(&coordinator->mutex); + pthread_cond_broadcast(&coordinator->all_stopped_cond); + pthread_mutex_unlock(&coordinator->mutex); + nanosleep(&delay, NULL); + atomic_store(&context->finished, true); + update_component_state(context->id, COMPONENT_STOPPED); + return NULL; +} + +void test_spurious_wake_does_not_claim_completion(void) { + spurious_context_t context = { + .id = register_component("spurious", COMPONENT_OTHER, NULL, 1), + .finished = false, + }; + pthread_t worker; + TEST_ASSERT_EQUAL_INT(0, pthread_create(&worker, NULL, wake_then_finish, &context)); + bool stopped = wait_for_all_components_stopped(2); + bool actually_finished = atomic_load(&context.finished); + TEST_ASSERT_EQUAL_INT(0, pthread_join(worker, NULL)); + TEST_ASSERT_TRUE(stopped); + TEST_ASSERT_TRUE_MESSAGE(actually_finished, "Condition wake is not component completion"); +} + +void test_repeated_restart_reuses_stopped_slots(void) { + int first = -1; + for (int generation = 0; generation < MAX_COMPONENTS * 2; generation++) { + int id = register_component("restarting_recorder", COMPONENT_OTHER, NULL, 1); + TEST_ASSERT_GREATER_OR_EQUAL_INT(0, id); + if (generation == 0) first = id; + TEST_ASSERT_EQUAL_INT(first, id); + TEST_ASSERT_EQUAL_INT(COMPONENT_RUNNING, get_component_state(id)); + update_component_state(id, COMPONENT_STOPPED); + TEST_ASSERT_TRUE(wait_for_all_components_stopped(0)); + } } /* ================================================================ @@ -183,6 +244,9 @@ int main(void) { RUN_TEST(test_wait_blocks_until_timeout_when_component_registered); RUN_TEST(test_wait_all_stopped_after_marking_stopped); RUN_TEST(test_wait_all_stopped_timeout_when_running); + RUN_TEST(test_wait_observes_actual_component_completion); + RUN_TEST(test_repeated_restart_reuses_stopped_slots); + RUN_TEST(test_spurious_wake_does_not_claim_completion); int result = UNITY_END(); shutdown_coordinator_cleanup(); diff --git a/tests/unit/test_storage_manager_retention.c b/tests/unit/test_storage_manager_retention.c index 4388c88f0..75d96a00f 100644 --- a/tests/unit/test_storage_manager_retention.c +++ b/tests/unit/test_storage_manager_retention.c @@ -2,9 +2,11 @@ #define _GNU_SOURCE #include +#include #include #include #include +#include #include #include #include @@ -19,15 +21,41 @@ #include "database/db_recordings.h" #include "database/db_streams.h" #include "storage/storage_manager.h" +#include "storage/storage_deletion.h" #include "utils/strings.h" #include "video/recording_path.h" -#define TEST_DB_PATH "/tmp/lightnvr_unit_storage_manager_retention.db" +static char test_db_path[PATH_MAX]; +static char test_db_dir[] = "/tmp/lightnvr-retention-db-XXXXXX"; +#define TEST_DB_PATH test_db_path static char g_storage_root[PATH_MAX]; +static char fault_path[PATH_MAX]; +static atomic_int fault_errno, fault_hits; + +int __real_unlink(const char *path); +int __wrap_unlink(const char *path) { + if (fault_errno && strcmp(path, fault_path) == 0) { + fault_hits++; + errno = fault_errno; + return -1; + } + return __real_unlink(path); +} + +static void assert_database_integrity(void) { + sqlite3_stmt *stmt = NULL; + TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_prepare_v2(get_db_handle(), + "PRAGMA integrity_check", -1, &stmt, NULL)); + TEST_ASSERT_EQUAL_INT(SQLITE_ROW, sqlite3_step(stmt)); + TEST_ASSERT_EQUAL_STRING("ok", (const char *)sqlite3_column_text(stmt, 0)); + sqlite3_finalize(stmt); +} static void clear_tables(void) { sqlite3 *db = get_db_handle(); + TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_exec(db, + "DELETE FROM storage_deletion_objects; DELETE FROM storage_deletions;", NULL, NULL, NULL)); sqlite3_exec(db, "DELETE FROM recordings;", NULL, NULL, NULL); sqlite3_exec(db, "DELETE FROM streams;", NULL, NULL, NULL); } @@ -162,6 +190,8 @@ static int count_recordings(void) { } void setUp(void) { + fault_errno = fault_hits = 0; + fault_path[0] = 0; clear_tables(); strcpy(g_storage_root, "/tmp/lightnvr_storage_manager_retentionXXXXXX"); TEST_ASSERT_NOT_NULL(mkdtemp(g_storage_root)); @@ -177,6 +207,7 @@ void setUp(void) { } void tearDown(void) { + fault_errno = 0; shutdown_storage_manager(); remove_tree(g_storage_root); g_storage_root[0] = '\0'; @@ -432,7 +463,144 @@ void test_apply_retention_policy_expires_recordings_of_deleted_streams(void) { assert_recording_kept(ghost_new); } +void test_enabled_retention_modes_preserve_protected_and_open_media(void) { + char inherited[PATH_MAX], unlimited[PATH_MAX], finite[PATH_MAX], recent[PATH_MAX]; + char protected_path[PATH_MAX]; + create_mp4_dir(); + TEST_ASSERT_EQUAL_INT(0, set_retention_days(14)); + add_stream_with_retention("inherit", true, -1, -1); + add_stream_with_retention("unlimited", true, 0, 0); + add_stream_with_retention("finite", true, 3, 3); + add_recording_aged("inherit", "inherit.mp4", 20 * 86400, inherited, sizeof(inherited)); + add_recording_aged("unlimited", "unlimited.mp4", 400 * 86400, unlimited, sizeof(unlimited)); + add_recording_aged("finite", "finite.mp4", 5 * 86400, finite, sizeof(finite)); + add_recording_aged("finite", "recent.mp4", 86400, recent, sizeof(recent)); + uint64_t id = add_recording_aged("finite", "protected.mp4", 400 * 86400, + protected_path, sizeof(protected_path)); + TEST_ASSERT_EQUAL_INT(0, set_recording_protected(id, true)); + int held_fd = open(protected_path, O_RDONLY); + TEST_ASSERT_GREATER_OR_EQUAL_INT(0, held_fd); + TEST_ASSERT_EQUAL_INT(2, apply_retention_policy()); + assert_recording_deleted(inherited); + assert_recording_deleted(finite); + assert_recording_kept(unlimited); + assert_recording_kept(recent); + assert_recording_kept(protected_path); + char byte = 1; + TEST_ASSERT_EQUAL_INT(1, read(held_fd, &byte, 1)); + TEST_ASSERT_EQUAL_INT(0, byte); + close(held_fd); + TEST_ASSERT_EQUAL_INT(3, count_recordings()); + assert_database_integrity(); +} + +static void check_unlink_fault_and_recovery(int error) { + char protected_path[PATH_MAX]; + create_mp4_dir(); + add_stream_with_retention("fault", true, 1, 1); + uint64_t id = add_recording_aged("fault", "fault.mp4", 3 * 86400, + fault_path, sizeof(fault_path)); + uint64_t protected_id = add_recording_aged("fault", "protected.mp4", 4 * 86400, + protected_path, sizeof(protected_path)); + TEST_ASSERT_EQUAL_INT(0, set_recording_protected(protected_id, true)); + fault_errno = error; + TEST_ASSERT_EQUAL_INT(0, apply_retention_policy()); + TEST_ASSERT_GREATER_THAN_INT(0, fault_hits); + assert_recording_kept(fault_path); + assert_recording_kept(protected_path); + sqlite3_stmt *stmt = NULL; + TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_prepare_v2(get_db_handle(), + "SELECT r.deletion_pending,o.state,o.attempt_count FROM recordings r " + "JOIN storage_deletions d ON d.recording_id=r.id " + "JOIN storage_deletion_objects o ON o.deletion_uuid=d.uuid WHERE r.id=?", -1, &stmt, NULL)); + sqlite3_bind_int64(stmt, 1, (sqlite3_int64)id); + TEST_ASSERT_EQUAL_INT(SQLITE_ROW, sqlite3_step(stmt)); + TEST_ASSERT_EQUAL_INT(1, sqlite3_column_int(stmt, 0)); + TEST_ASSERT_EQUAL_STRING("retry_wait", (const char *)sqlite3_column_text(stmt, 1)); + TEST_ASSERT_EQUAL_INT(1, sqlite3_column_int(stmt, 2)); + sqlite3_finalize(stmt); + assert_database_integrity(); + fault_errno = 0; + /* Advance this disposable journal's retry deadline, avoiding a 30s sleep. */ + TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_exec(get_db_handle(), + "UPDATE storage_deletion_objects SET next_attempt_at=0 WHERE state='retry_wait'", NULL, NULL, NULL)); + TEST_ASSERT_GREATER_THAN_INT(0, storage_deletion_process_one()); + assert_recording_deleted(fault_path); + assert_recording_kept(protected_path); + assert_database_integrity(); +} +void test_readonly_cleanup_preserves_metadata_and_recovers(void) { check_unlink_fault_and_recovery(EROFS); } +void test_io_failure_cleanup_preserves_metadata_and_recovers(void) { check_unlink_fault_and_recovery(EIO); } + +void test_unavailable_root_preserves_expired_metadata_until_restored(void) { + char expired[PATH_MAX], protected_path[PATH_MAX], offline[PATH_MAX]; + create_mp4_dir(); + add_stream_with_retention("offline", true, 1, 1); + add_recording_aged("offline", "expired.mp4", 3 * 86400, expired, sizeof(expired)); + uint64_t id = add_recording_aged("offline", "protected.mp4", 4 * 86400, + protected_path, sizeof(protected_path)); + TEST_ASSERT_EQUAL_INT(0, set_recording_protected(id, true)); + snprintf(offline, sizeof(offline), "%s-offline", g_storage_root); + TEST_ASSERT_EQUAL_INT(0, rename(g_storage_root, offline)); + int deleted = apply_retention_policy(); + int count = count_recordings(); + /* Always restore before assertions, including a failing regression. */ + TEST_ASSERT_EQUAL_INT(0, rename(offline, g_storage_root)); + TEST_ASSERT_EQUAL_INT(0, deleted); + TEST_ASSERT_EQUAL_INT(2, count); + assert_recording_kept(expired); + assert_recording_kept(protected_path); + TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_exec(get_db_handle(), + "UPDATE storage_deletion_objects SET next_attempt_at=0 WHERE state='retry_wait'", NULL, NULL, NULL)); + TEST_ASSERT_GREATER_THAN_INT(0, storage_deletion_process_one()); + assert_recording_deleted(expired); + assert_recording_kept(protected_path); + assert_database_integrity(); +} + +void test_healthy_root_missing_file_cleanup_is_idempotent(void) { + char missing[PATH_MAX]; + create_mp4_dir(); + add_stream_with_retention("missing", true, 1, 1); + uint64_t id = add_recording_aged("missing", "missing.mp4", 3 * 86400, + missing, sizeof(missing)); + TEST_ASSERT_EQUAL_INT(0, unlink(missing)); + uint64_t removed = 99; + TEST_ASSERT_EQUAL_INT(0, storage_recording_delete(id, "missing-file control", &removed)); + TEST_ASSERT_EQUAL_UINT64(0, removed); + assert_recording_deleted(missing); + TEST_ASSERT_EQUAL_INT(0, storage_recording_delete(id, "idempotent control", &removed)); + TEST_ASSERT_EQUAL_UINT64(0, removed); + assert_database_integrity(); +} + +void test_unknown_legacy_root_preserves_missing_metadata(void) { + char missing[PATH_MAX], configured[PATH_MAX]; + create_mp4_dir(); + snprintf(missing, sizeof(missing), "%s/unattributed.mp4", g_storage_root); + snprintf(configured, sizeof(configured), "%s/mp4", g_storage_root); + recording_metadata_t rec = make_recording("unknown", missing, time(NULL) - 86400, 1024); + uint64_t id = add_recording_metadata(&rec); + TEST_ASSERT_NOT_EQUAL(0, id); + /* Both directories are owned, but the legacy row is outside the configured + * root: the root's health cannot establish this object's absence. */ + safe_strcpy(g_config.storage_path, configured, sizeof(g_config.storage_path), 0); + uint64_t removed = 99; + int result = storage_recording_delete(id, "unknown-root control", &removed); + safe_strcpy(g_config.storage_path, g_storage_root, sizeof(g_config.storage_path), 0); + TEST_ASSERT_EQUAL_INT(1, result); + TEST_ASSERT_EQUAL_UINT64(0, removed); + TEST_ASSERT_EQUAL_INT(0, get_recording_metadata_by_id(id, &rec)); + TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_exec(get_db_handle(), + "UPDATE storage_deletion_objects SET next_attempt_at=0 WHERE state='retry_wait'", NULL, NULL, NULL)); + TEST_ASSERT_GREATER_THAN_INT(0, storage_deletion_process_one()); + assert_recording_deleted(missing); + assert_database_integrity(); +} + int main(void) { + if (!mkdtemp(test_db_dir)) return 1; + snprintf(test_db_path, sizeof(test_db_path), "%s/metadata.db", test_db_dir); unlink(TEST_DB_PATH); if (init_database(TEST_DB_PATH) != 0) return 1; @@ -447,9 +615,15 @@ int main(void) { RUN_TEST(test_apply_retention_policy_honours_disabled_stream_explicit_retention); RUN_TEST(test_apply_retention_policy_expires_recordings_of_deleted_streams); RUN_TEST(test_cleanup_pace_sleeps_at_least_as_long_as_the_database_work); + RUN_TEST(test_enabled_retention_modes_preserve_protected_and_open_media); + RUN_TEST(test_readonly_cleanup_preserves_metadata_and_recovers); + RUN_TEST(test_io_failure_cleanup_preserves_metadata_and_recovers); + RUN_TEST(test_unavailable_root_preserves_expired_metadata_until_restored); + RUN_TEST(test_healthy_root_missing_file_cleanup_is_idempotent); + RUN_TEST(test_unknown_legacy_root_preserves_missing_metadata); int result = UNITY_END(); shutdown_database(); - unlink(TEST_DB_PATH); + remove_tree(test_db_dir); return result; -} \ No newline at end of file +} diff --git a/tests/unit/test_storage_retention_sqlite.c b/tests/unit/test_storage_retention_sqlite.c index f5a11defa..bc476f15d 100644 --- a/tests/unit/test_storage_retention_sqlite.c +++ b/tests/unit/test_storage_retention_sqlite.c @@ -24,12 +24,15 @@ #include #include "unity.h" +#include "core/config.h" #include "utils/strings.h" #include "database/db_core.h" #include "database/db_recordings.h" /* ---- test database path ---- */ -#define TEST_DB_PATH "/tmp/lightnvr_unit_retention_test.db" +static char test_db_dir[] = "/tmp/lightnvr-retention-sqlite-XXXXXX"; +static char test_db_path[256]; +#define TEST_DB_PATH test_db_path /* ---- helpers ---- */ @@ -67,7 +70,7 @@ static void clear_recordings(void) { /* ---- Unity boilerplate ---- */ void setUp(void) { clear_recordings(); } -void tearDown(void) {} +void tearDown(void) { sqlite3_exec(get_db_handle(), "PRAGMA query_only=OFF; PRAGMA max_page_count=1073741823;", NULL, NULL, NULL); } /* ================================================================ * Tests @@ -438,7 +441,9 @@ void test_pressure_cleanup_is_scoped_to_storage_target(void) { void test_delete_recording_removes_it(void) { time_t now = time(NULL); - recording_metadata_t m = make_recording("cam1", "/rec/del.mp4", + char missing_path[sizeof(test_db_path)]; + snprintf(missing_path, sizeof(missing_path), "%s/deleted.mp4", test_db_dir); + recording_metadata_t m = make_recording("cam1", missing_path, now - 10 * 86400, "scheduled", false); uint64_t id = add_recording_metadata(&m); @@ -462,7 +467,78 @@ void test_delete_recording_removes_it(void) { * main — init DB once, run all tests, shutdown DB * ================================================================ */ +static int scalar_int(const char *sql) { + sqlite3_stmt *stmt = NULL; + TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_prepare_v2(get_db_handle(), sql, -1, &stmt, NULL)); + TEST_ASSERT_EQUAL_INT(SQLITE_ROW, sqlite3_step(stmt)); + int value = sqlite3_column_int(stmt, 0); + sqlite3_finalize(stmt); + return value; +} + +static void check_existing_record_and_integrity(uint64_t id) { + recording_metadata_t loaded; + TEST_ASSERT_EQUAL_INT(0, get_recording_metadata_by_id(id, &loaded)); + TEST_ASSERT_TRUE(loaded.protected); + TEST_ASSERT_EQUAL_STRING("/rec/preserved.mp4", loaded.file_path); + sqlite3_stmt *stmt = NULL; + TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_prepare_v2(get_db_handle(), "PRAGMA integrity_check", -1, &stmt, NULL)); + TEST_ASSERT_EQUAL_INT(SQLITE_ROW, sqlite3_step(stmt)); + TEST_ASSERT_EQUAL_STRING("ok", (const char *)sqlite3_column_text(stmt, 0)); + sqlite3_finalize(stmt); +} + +void test_sqlite_readonly_rejects_update_without_losing_metadata_then_recovers(void) { + recording_metadata_t m = make_recording("cam", "/rec/preserved.mp4", time(NULL), "scheduled", true); + uint64_t id = add_recording_metadata(&m); + TEST_ASSERT_NOT_EQUAL(0, id); + TEST_ASSERT_EQUAL_INT(0, set_recording_protected(id, true)); + TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_exec(get_db_handle(), "PRAGMA query_only=ON", NULL, NULL, NULL)); + TEST_ASSERT_NOT_EQUAL(0, set_recording_protected(id, false)); + TEST_ASSERT_EQUAL_INT(SQLITE_READONLY, sqlite3_errcode(get_db_handle())); + check_existing_record_and_integrity(id); + TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_exec(get_db_handle(), "PRAGMA query_only=OFF", NULL, NULL, NULL)); + TEST_ASSERT_EQUAL_INT(0, set_recording_protected(id, false)); + recording_metadata_t loaded; + TEST_ASSERT_EQUAL_INT(0, get_recording_metadata_by_id(id, &loaded)); + TEST_ASSERT_FALSE(loaded.protected); +} + +void test_sqlite_full_rejects_new_recording_without_corruption_then_recovers(void) { + recording_metadata_t m = make_recording("cam", "/rec/preserved.mp4", time(NULL), "scheduled", true); + uint64_t id = add_recording_metadata(&m); + TEST_ASSERT_NOT_EQUAL(0, id); + TEST_ASSERT_EQUAL_INT(0, set_recording_protected(id, true)); + int pages = scalar_int("PRAGMA page_count"); + char sql[128]; + snprintf(sql, sizeof(sql), "PRAGMA max_page_count=%d", pages + 8); + TEST_ASSERT_EQUAL_INT(pages + 8, scalar_int(sql)); + /* Bound the real SQLite pager to eight extra pages. No host disk filling. */ + recording_metadata_t next = make_recording("cam", "/rec/full.mp4", time(NULL), "scheduled", false); + memset(next.file_path, 'x', sizeof(next.file_path) - 1); + next.file_path[sizeof(next.file_path) - 1] = 0; + int inserted = 0; + for (; inserted < 256; inserted++) { + char prefix[16]; + int length = snprintf(prefix, sizeof(prefix), "/rec/%03d-", inserted); + memcpy(next.file_path, prefix, (size_t)length); + if (add_recording_metadata(&next) == 0) break; + } + TEST_ASSERT_LESS_THAN_INT(256, inserted); + TEST_ASSERT_EQUAL_INT(SQLITE_FULL, sqlite3_errcode(get_db_handle())); + TEST_ASSERT_EQUAL_INT(inserted + 1, scalar_int("SELECT COUNT(*) FROM recordings")); + check_existing_record_and_integrity(id); + TEST_ASSERT_GREATER_THAN_INT(pages, scalar_int("PRAGMA max_page_count=1073741823")); + safe_strcpy(next.file_path, "/rec/recovered.mp4", sizeof(next.file_path), 0); + TEST_ASSERT_NOT_EQUAL(0, add_recording_metadata(&next)); + TEST_ASSERT_EQUAL_INT(inserted + 2, scalar_int("SELECT COUNT(*) FROM recordings")); + check_existing_record_and_integrity(id); +} + int main(void) { + if (!mkdtemp(test_db_dir)) return 1; + safe_strcpy(g_config.storage_path, test_db_dir, sizeof(g_config.storage_path), 0); + snprintf(test_db_path, sizeof(test_db_path), "%s/metadata.db", test_db_dir); /* Remove stale test DB from a previous run */ unlink(TEST_DB_PATH); @@ -488,11 +564,14 @@ int main(void) { RUN_TEST(test_pressure_cleanup_deprioritizes_overrides_and_detection_within_tier); RUN_TEST(test_pressure_cleanup_is_scoped_to_storage_target); RUN_TEST(test_delete_recording_removes_it); + RUN_TEST(test_sqlite_readonly_rejects_update_without_losing_metadata_then_recovers); + RUN_TEST(test_sqlite_full_rejects_new_recording_without_corruption_then_recovers); int result = UNITY_END(); shutdown_database(); unlink(TEST_DB_PATH); + rmdir(test_db_dir); return result; } diff --git a/tests/unit/test_stream_state_disposal.c b/tests/unit/test_stream_state_disposal.c new file mode 100644 index 000000000..c498a491d --- /dev/null +++ b/tests/unit/test_stream_state_disposal.c @@ -0,0 +1,174 @@ +/* Focused registry-disposal regressions; media stop calls are deterministic + * link seams so no camera, worker thread or external service is needed. */ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include +#include + +#include "unity.h" +#include "core/config.h" +#include "video/stream_state.h" + +static uintptr_t tracked[4]; +static unsigned destroys[4]; +static int destroy_error; +static bool check_reentry; +static unsigned stop_calls; + +int __real_pthread_mutex_destroy(pthread_mutex_t *mutex); +int __wrap_pthread_mutex_destroy(pthread_mutex_t *mutex) { + for (size_t i = 0; i < 4; i++) { + if (tracked[i] == (uintptr_t)mutex) destroys[i]++; + } + int result = __real_pthread_mutex_destroy(mutex); + if (result != 0) destroy_error = result; + return result; +} + +static stream_config_t config_for(const char *name) { + stream_config_t config; + memset(&config, 0, sizeof(config)); + snprintf(config.name, sizeof(config.name), "%s", name); + snprintf(config.url, sizeof(config.url), "rtsp://127.0.0.1/synthetic"); + config.enabled = true; + config.streaming_enabled = true; + return config; +} + +int __wrap_stream_stop_hls(const char *name) { + stop_calls++; + if (check_reentry) { + /* Real HLS shutdown performs this same registry lookup. */ + stream_state_manager_t *state = get_stream_state_by_name(name); + if (!state || strcmp(state->name, name) != 0) _exit(11); + if (get_stream_state_count() != 1) _exit(12); + stream_config_t late = config_for("late-admission"); + if (create_stream_state(&late) != NULL) _exit(13); + if (remove_stream_state(state) != -1) _exit(14); + shutdown_stream_state_manager(); /* Must be a no-op during teardown. */ + if (get_stream_state_by_name(name) != state) _exit(15); + } + return 0; +} + +int __wrap_stop_mp4_recording(const char *name) { (void)name; return 0; } +void __wrap_reset_timestamp_tracker(const char *name) { (void)name; } +void __wrap_remove_timestamp_tracker(const char *name) { (void)name; } + +void setUp(void) { + memset(tracked, 0, sizeof(tracked)); + memset(destroys, 0, sizeof(destroys)); + destroy_error = 0; + check_reentry = false; + stop_calls = 0; +} +void tearDown(void) { shutdown_stream_state_manager(); } + +static void track(stream_state_manager_t *state, size_t slot) { + tracked[slot] = (uintptr_t)&state->mutex; + tracked[slot + 1] = (uintptr_t)&state->state_mutex; +} + +void test_removal_destroys_both_mutexes_once(void) { + TEST_ASSERT_EQUAL_INT(0, init_stream_state_manager(2)); + stream_config_t config = config_for("remove"); + stream_state_manager_t *state = create_stream_state(&config); + TEST_ASSERT_NOT_NULL(state); + track(state, 0); + TEST_ASSERT_EQUAL_INT(0, remove_stream_state(state)); + TEST_ASSERT_EQUAL_UINT(1, destroys[0]); + TEST_ASSERT_EQUAL_UINT(1, destroys[1]); + TEST_ASSERT_EQUAL_INT(0, destroy_error); + TEST_ASSERT_NULL(get_stream_state_by_name("remove")); +} + +void test_extra_reference_refusal_destroys_neither_mutex(void) { + TEST_ASSERT_EQUAL_INT(0, init_stream_state_manager(2)); + stream_config_t config = config_for("borrowed"); + stream_state_manager_t *state = create_stream_state(&config); + TEST_ASSERT_NOT_NULL(state); + track(state, 0); + TEST_ASSERT_EQUAL_INT(2, stream_state_add_ref(state, STREAM_COMPONENT_HLS)); + TEST_ASSERT_EQUAL_INT(-1, remove_stream_state(state)); + TEST_ASSERT_EQUAL_UINT(0, destroys[0]); + TEST_ASSERT_EQUAL_UINT(0, destroys[1]); + TEST_ASSERT_EQUAL_PTR(state, get_stream_state_by_name("borrowed")); + TEST_ASSERT_EQUAL_INT(1, stream_state_release_ref(state, STREAM_COMPONENT_HLS)); + TEST_ASSERT_EQUAL_INT(0, remove_stream_state(state)); + TEST_ASSERT_EQUAL_UINT(1, destroys[0]); + TEST_ASSERT_EQUAL_UINT(1, destroys[1]); + TEST_ASSERT_EQUAL_INT(0, destroy_error); +} + +void test_shutdown_destroys_every_mutex_and_is_idempotent(void) { + TEST_ASSERT_EQUAL_INT(0, init_stream_state_manager(2)); + stream_config_t first = config_for("first"), second = config_for("second"); + stream_state_manager_t *a = create_stream_state(&first); + stream_state_manager_t *b = create_stream_state(&second); + TEST_ASSERT_NOT_NULL(a); + TEST_ASSERT_NOT_NULL(b); + track(a, 0); track(b, 2); + shutdown_stream_state_manager(); + for (size_t i = 0; i < 4; i++) TEST_ASSERT_EQUAL_UINT(1, destroys[i]); + TEST_ASSERT_EQUAL_INT(0, destroy_error); + TEST_ASSERT_EQUAL_INT(0, get_stream_state_count()); + shutdown_stream_state_manager(); + for (size_t i = 0; i < 4; i++) TEST_ASSERT_EQUAL_UINT(1, destroys[i]); +} + +void test_repeated_lifecycle_disposes_once_per_generation(void) { + for (int cycle = 0; cycle < 32; cycle++) { + memset(tracked, 0, sizeof(tracked)); + memset(destroys, 0, sizeof(destroys)); + TEST_ASSERT_EQUAL_INT(0, init_stream_state_manager(1)); + stream_config_t config = config_for("repeat"); + stream_state_manager_t *state = create_stream_state(&config); + TEST_ASSERT_NOT_NULL(state); + track(state, 0); + if (cycle % 2 == 0) TEST_ASSERT_EQUAL_INT(0, remove_stream_state(state)); + shutdown_stream_state_manager(); + TEST_ASSERT_EQUAL_UINT(1, destroys[0]); + TEST_ASSERT_EQUAL_UINT(1, destroys[1]); + TEST_ASSERT_EQUAL_INT(0, destroy_error); + } +} + +void test_stop_callback_can_lookup_but_cannot_mutate_tearing_down_registry(void) { + fflush(NULL); + pid_t child = fork(); + TEST_ASSERT_GREATER_OR_EQUAL_INT(0, child); + if (child == 0) { + alarm(3); /* A registry lock recursion must fail, not hang ctest. */ + if (init_stream_state_manager(2) != 0) _exit(20); + stream_config_t config = config_for("reentrant"); + stream_state_manager_t *state = create_stream_state(&config); + if (!state) _exit(21); + track(state, 0); + state->state = STREAM_STATE_ACTIVE; + check_reentry = true; + shutdown_stream_state_manager(); + if (stop_calls != 1 || destroys[0] != 1 || destroys[1] != 1 || destroy_error) _exit(22); + if (get_stream_state_count() != 0) _exit(23); + alarm(0); + _exit(0); + } + int status = 0; + TEST_ASSERT_EQUAL_INT(child, waitpid(child, &status, 0)); + TEST_ASSERT_TRUE_MESSAGE(WIFEXITED(status), "shutdown stop callback exceeded its deadline (registry lock recursion)"); + TEST_ASSERT_EQUAL_INT_MESSAGE(0, WEXITSTATUS(status), "shutdown violated callback lookup or teardown ownership"); +} + +int main(void) { + UNITY_BEGIN(); + RUN_TEST(test_removal_destroys_both_mutexes_once); + RUN_TEST(test_extra_reference_refusal_destroys_neither_mutex); + RUN_TEST(test_shutdown_destroys_every_mutex_and_is_idempotent); + RUN_TEST(test_repeated_lifecycle_disposes_once_per_generation); + RUN_TEST(test_stop_callback_can_lookup_but_cannot_mutate_tearing_down_registry); + return UNITY_END(); +} diff --git a/tests/unit/test_t7_11.c b/tests/unit/test_t7_11.c new file mode 100644 index 000000000..0b893dce0 --- /dev/null +++ b/tests/unit/test_t7_11.c @@ -0,0 +1,162 @@ +/* T7.11: real private-DB exposure characterization and boundary controls. + * Plaintext characterization is evidence of an OPEN finding, not remediation. */ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "unity.h" +#include "core/config.h" +#include "core/logger.h" +#include "database/db_auth.h" +#include "database/db_core.h" +#include "database/db_event_destinations.h" +#include "web/api_handlers_event_destinations.h" +#include "web/request_response.h" + +static char directory[]="/tmp/lightnvr-t7-11-XXXXXX", database[256]; +static char admin_key[128], viewer_key[128], user_key[128]; +static const char *secret_create="T7_11_SYNTHETIC_CREATE_CREDENTIAL_620b"; +static const char *secret_update="T7_11_SYNTHETIC_UPDATE_CREDENTIAL_98c1"; + +static void sql(const char *statement) { + char *error=NULL;int status=sqlite3_exec(get_db_handle(),statement,NULL,NULL,&error); + if(error)fprintf(stderr,"SQL: %s\n",error); + sqlite3_free(error);TEST_ASSERT_EQUAL_INT(SQLITE_OK,status); +} +static event_destination_t destination(void) { + event_destination_t value={0}; + strcpy(value.name,"T7.11 destination");strcpy(value.destination_type,"mqtt"); + strcpy(value.broker_host,"mqtt.synthetic.invalid");value.broker_port=8883; + strcpy(value.client_id,"t7-11");strcpy(value.topic_template,EVENT_DESTINATION_DEFAULT_TOPIC_TEMPLATE); + strcpy(value.username,"synthetic-publisher");strcpy(value.tls_mode,"system"); + value.keepalive_seconds=60;value.qos=1;return value; +} +static event_destination_t create(void) { + event_destination_t value=destination(); + TEST_ASSERT_EQUAL_INT(DB_EVENT_DESTINATION_OK,db_event_destination_create(&value,secret_create));return value; +} +static void assert_private_fixture(void) { + struct stat info;TEST_ASSERT_EQUAL_INT(0,stat(directory,&info));TEST_ASSERT_EQUAL_INT(0700,info.st_mode&0777); + TEST_ASSERT_EQUAL_INT(0,stat(database,&info));TEST_ASSERT_EQUAL_INT(0600,info.st_mode&0777); +} +static void assert_sqlite_plaintext(const char *expected) { + sqlite3 *reader=NULL;sqlite3_stmt *statement=NULL; + TEST_ASSERT_EQUAL_INT(SQLITE_OK,sqlite3_open_v2(database,&reader,SQLITE_OPEN_READONLY,NULL)); + TEST_ASSERT_EQUAL_INT(SQLITE_OK,sqlite3_prepare_v2(reader,"SELECT password FROM event_destinations",-1,&statement,NULL)); + TEST_ASSERT_EQUAL_INT(SQLITE_ROW,sqlite3_step(statement)); + TEST_ASSERT_EQUAL_STRING(expected,(const char *)sqlite3_column_text(statement,0)); + sqlite3_finalize(statement);sqlite3_close(reader); +} +static void assert_plaintext_on_disk(const char *expected) { + TEST_ASSERT_EQUAL_INT(SQLITE_OK,sqlite3_wal_checkpoint_v2(get_db_handle(),NULL,SQLITE_CHECKPOINT_TRUNCATE,NULL,NULL)); + FILE *file=fopen(database,"rb");TEST_ASSERT_NOT_NULL(file); + TEST_ASSERT_EQUAL_INT(0,fseek(file,0,SEEK_END));long length=ftell(file);TEST_ASSERT_GREATER_THAN(0,length); + rewind(file);unsigned char *bytes=malloc((size_t)length);TEST_ASSERT_NOT_NULL(bytes); + TEST_ASSERT_EQUAL_size_t((size_t)length,fread(bytes,1,(size_t)length,file));fclose(file); + bool found=false;size_t needle=strlen(expected); + for(size_t i=0;i+needle<=(size_t)length;i++)if(memcmp(bytes+i,expected,needle)==0){found=true;break;} + free(bytes);TEST_ASSERT_TRUE_MESSAGE(found,"Characterization must reproduce cleartext on disk; this is not a security pass"); +} +static void assert_secret(const event_destination_t *value,const char *expected) { + char password[EVENT_DESTINATION_PASSWORD_MAX];memset(password,0xa5,sizeof(password)); + TEST_ASSERT_EQUAL_INT(DB_EVENT_DESTINATION_OK,db_event_destination_get_password(value->uuid,value->revision,password,sizeof(password))); + TEST_ASSERT_EQUAL_STRING(expected,password);memset(password,0,sizeof(password)); +} +static cJSON *request(void (*handler)(const http_request_t *,http_response_t *),http_method_t method, + const char *path,const char *body,const char *key,int expected) { + http_request_t req;http_response_t res;http_request_init(&req);http_response_init(&res); + req.method=method;snprintf(req.method_str,sizeof(req.method_str),"%s",method==HTTP_METHOD_GET?"GET":method==HTTP_METHOD_POST?"POST":"PUT"); + snprintf(req.path,sizeof(req.path),"%s",path);snprintf(req.uri,sizeof(req.uri),"%s",path); + strcpy(req.client_ip,"127.0.0.1");req.body=(void *)body;req.body_len=body?strlen(body):0; + if(key){strcpy(req.headers[0].name,"X-API-Key");snprintf(req.headers[0].value,sizeof(req.headers[0].value),"%s",key);req.num_headers=1;} + handler(&req,&res);TEST_ASSERT_EQUAL_INT(expected,res.status_code); + TEST_ASSERT_NOT_NULL(res.body);TEST_ASSERT_NULL(strstr(res.body,secret_create));TEST_ASSERT_NULL(strstr(res.body,secret_update)); + cJSON *json=cJSON_Parse(res.body);TEST_ASSERT_NOT_NULL(json);http_response_free(&res);return json; +} +static void assert_zero(const unsigned char *bytes,size_t length){for(size_t i=0;ivaluestring); + cJSON *auth=cJSON_GetObjectItemCaseSensitive(json,"authentication");TEST_ASSERT_NULL(cJSON_GetObjectItemCaseSensitive(auth,"password")); + TEST_ASSERT_TRUE(cJSON_IsTrue(cJSON_GetObjectItemCaseSensitive(auth,"password_configured")));cJSON_Delete(json); + cJSON_Delete(request(handle_get_event_destination,HTTP_METHOD_GET,path,NULL,admin_key,200)); + cJSON_Delete(request(handle_get_event_destinations,HTTP_METHOD_GET,"/api/event-destinations",NULL,admin_key,200)); + snprintf(body,sizeof(body),"{\"revision\":1,\"authentication\":{\"password\":\"%s\"}}",secret_update); + cJSON_Delete(request(handle_put_event_destination,HTTP_METHOD_PUT,path,body,admin_key,200)); + assert_sqlite_plaintext(secret_update); + sqlite3_stmt *statement=NULL;TEST_ASSERT_EQUAL_INT(SQLITE_OK,sqlite3_prepare_v2(get_db_handle(),"SELECT details_json FROM audit_events",-1,&statement,NULL)); + int count=0;while(sqlite3_step(statement)==SQLITE_ROW){const char *details=(const char *)sqlite3_column_text(statement,0);TEST_ASSERT_NULL(strstr(details,secret_create));TEST_ASSERT_NULL(strstr(details,secret_update));count++;} + sqlite3_finalize(statement);TEST_ASSERT_GREATER_THAN(0,count); +} +static void assert_denied(const char *key,int status){ + event_destination_t value=create();char path[128];snprintf(path,sizeof(path),"/api/event-destinations/%s",value.uuid); + cJSON_Delete(request(handle_get_event_destinations,HTTP_METHOD_GET,"/api/event-destinations",NULL,key,status)); + cJSON_Delete(request(handle_get_event_destination,HTTP_METHOD_GET,path,NULL,key,status)); + cJSON_Delete(request(handle_post_event_destination,HTTP_METHOD_POST,"/api/event-destinations","{}",key,status)); + cJSON_Delete(request(handle_put_event_destination,HTTP_METHOD_PUT,path,"{}",key,status)); + TEST_ASSERT_EQUAL_INT(1,db_event_destination_count());assert_secret(&value,secret_create); + event_destination_t current;TEST_ASSERT_EQUAL_INT(DB_EVENT_DESTINATION_OK,db_event_destination_get(value.uuid,¤t));TEST_ASSERT_EQUAL_INT64(value.revision,current.revision); +} +void test_anonymous_requests_cannot_read_or_mutate(void){assert_denied(NULL,401);} +void test_viewer_requests_cannot_read_or_mutate(void){assert_denied(viewer_key,403);} +void test_standard_user_requests_cannot_read_or_mutate(void){assert_denied(user_key,403);} +void test_runtime_getter_clears_output_on_stale_invalid_missing_and_small_buffer(void){ + event_destination_t value=create();unsigned char password[EVENT_DESTINATION_PASSWORD_MAX]; + memset(password,0xa5,sizeof(password));TEST_ASSERT_EQUAL_INT(DB_EVENT_DESTINATION_STALE,db_event_destination_get_password(value.uuid,value.revision+1,(char*)password,sizeof(password)));assert_zero(password,sizeof(password)); + memset(password,0xa5,sizeof(password));TEST_ASSERT_EQUAL_INT(DB_EVENT_DESTINATION_INVALID,db_event_destination_get_password("invalid",1,(char*)password,sizeof(password)));assert_zero(password,sizeof(password)); + memset(password,0xa5,sizeof(password));TEST_ASSERT_EQUAL_INT(DB_EVENT_DESTINATION_NOT_FOUND,db_event_destination_get_password("00000000-0000-4000-8000-000000000001",1,(char*)password,sizeof(password)));assert_zero(password,sizeof(password)); + memset(password,0xa5,sizeof(password));TEST_ASSERT_EQUAL_INT(DB_EVENT_DESTINATION_ERROR,db_event_destination_get_password(value.uuid,value.revision,(char*)password,4));assert_zero(password,4);TEST_ASSERT_EQUAL_UINT8(0xa5,password[4]); +} +void test_omitted_password_preserves_and_explicit_clear_removes_logical_value(void){ + event_destination_t value=create();strcpy(value.description,"noncredential update"); + TEST_ASSERT_EQUAL_INT(DB_EVENT_DESTINATION_OK,db_event_destination_update(&value,value.revision,NULL,false));assert_secret(&value,secret_create); + TEST_ASSERT_EQUAL_INT(DB_EVENT_DESTINATION_OK,db_event_destination_update(&value,value.revision,NULL,true));assert_secret(&value,"");TEST_ASSERT_FALSE(value.password_configured); +} +static int user_key_create(const char *name,user_role_t role,char key[128]){int64_t id=0;return db_auth_create_user(name,"synthetic-test-passphrase",NULL,role,true,&id)||db_auth_generate_api_key(id,key,128);} +int main(void){ + mode_t previous=umask(0077);if(!mkdtemp(directory))return 1; + snprintf(database,sizeof(database),"%s/private.db",directory);init_logger();load_default_config(&g_config); + if(init_database_ex(database,DB_INIT_NO_CHECK|DB_INIT_NO_BACKUP)||db_auth_init()||user_key_create("t7_11_admin",USER_ROLE_ADMIN,admin_key)||user_key_create("t7_11_viewer",USER_ROLE_VIEWER,viewer_key)||user_key_create("t7_11_user",USER_ROLE_USER,user_key))return 1; + UNITY_BEGIN(); + RUN_TEST(test_codeql_316_create_reproduces_plaintext_in_private_database); + RUN_TEST(test_codeql_307_replace_reproduces_plaintext_in_private_database); + RUN_TEST(test_admin_create_get_list_update_and_audit_are_redacted); + RUN_TEST(test_anonymous_requests_cannot_read_or_mutate); + RUN_TEST(test_viewer_requests_cannot_read_or_mutate); + RUN_TEST(test_standard_user_requests_cannot_read_or_mutate); + RUN_TEST(test_runtime_getter_clears_output_on_stale_invalid_missing_and_small_buffer); + RUN_TEST(test_omitted_password_preserves_and_explicit_clear_removes_logical_value); + int result=UNITY_END();shutdown_database();unlink(database); + char sidecar[280];snprintf(sidecar,sizeof(sidecar),"%s-wal",database);unlink(sidecar);snprintf(sidecar,sizeof(sidecar),"%s-shm",database);unlink(sidecar); + /* db_auth_init also opens the DB and creates its initial backup. Remove + * only regular files inside this exact owned private backup directory. */ + char backup_dir[280];snprintf(backup_dir,sizeof(backup_dir),"%s.backups",database); + DIR *backups=opendir(backup_dir); + if(backups){struct dirent *entry;while((entry=readdir(backups))){ + if(!strcmp(entry->d_name,".")||!strcmp(entry->d_name,".."))continue; + char backup[600];snprintf(backup,sizeof(backup),"%s/%s",backup_dir,entry->d_name); + struct stat info;if(lstat(backup,&info)||!S_ISREG(info.st_mode)||unlink(backup))result=1; + }closedir(backups);if(rmdir(backup_dir))result=1;} + snprintf(sidecar,sizeof(sidecar),"%s.bak",database);unlink(sidecar); + if(rmdir(directory)){fprintf(stderr,"Owned private fixture cleanup failed: %s\n",directory);result=1;} + umask(previous);return result; +} diff --git a/tests/unit/test_t7_16.c b/tests/unit/test_t7_16.c new file mode 100644 index 000000000..d84fcd30b --- /dev/null +++ b/tests/unit/test_t7_16.c @@ -0,0 +1,161 @@ +/* CODEQL-329: exercise the production metadata assignment, with explicit + * seams only for channel counts the real decoder/encoder cannot accept. */ +#include +#include +#include +#include "unity.h" +#include "video/faac_encoder.h" +#include "video/mp4_writer_internal.h" + +static bool synthetic; +static int synthetic_channels; +static faac_encoder_config_t captured; +static unsigned char fake_encoder; +static int encoder_opens; +static AVCodecParameters *input, *output; + +int __real_avcodec_open2(AVCodecContext *, const AVCodec *, AVDictionary **); +int __wrap_avcodec_open2(AVCodecContext *ctx, const AVCodec *codec, AVDictionary **opts) { + int ret = __real_avcodec_open2(ctx, codec, opts); + if (ret >= 0 && synthetic) { + av_channel_layout_uninit(&ctx->ch_layout); + ctx->ch_layout.order = AV_CHANNEL_ORDER_UNSPEC; + ctx->ch_layout.nb_channels = synthetic_channels; + } + return ret; +} +faac_adapter_t *__real_faac_adapter_open(const faac_encoder_config_t *); +faac_adapter_t *__wrap_faac_adapter_open(const faac_encoder_config_t *cfg) { + encoder_opens++; + captured = *cfg; + return synthetic ? (faac_adapter_t *)&fake_encoder : __real_faac_adapter_open(cfg); +} +#define WRAP_GETTER(type, name, value) \ + type __real_##name(faac_adapter_t *); \ + type __wrap_##name(faac_adapter_t *a) { \ + return synthetic ? (value) : __real_##name(a); \ + } +WRAP_GETTER(uint32_t, faac_adapter_get_sample_rate, captured.sample_rate) +WRAP_GETTER(uint32_t, faac_adapter_get_frame_samples, 1024) +WRAP_GETTER(uint32_t, faac_adapter_get_encoder_delay, 0) +WRAP_GETTER(enum faac_object_type, faac_adapter_get_object_type, FAAC_OBJ_LOW) +int __real_faac_adapter_get_asc(faac_adapter_t *, const uint8_t **, size_t *); +int __wrap_faac_adapter_get_asc(faac_adapter_t *a, const uint8_t **asc, size_t *size) { + static const uint8_t fixture[] = {0x11, 0x90}; + if (!synthetic) return __real_faac_adapter_get_asc(a, asc, size); + *asc = fixture; + *size = sizeof(fixture); + return 0; +} +void __real_faac_adapter_close(faac_adapter_t **); +void __wrap_faac_adapter_close(faac_adapter_t **a) { + if (synthetic) *a = NULL; + else __real_faac_adapter_close(a); +} + +void setUp(void) { + synthetic = false; + encoder_opens = 0; + output = NULL; + input = avcodec_parameters_alloc(); + TEST_ASSERT_NOT_NULL(input); + input->codec_type = AVMEDIA_TYPE_AUDIO; + input->codec_id = AV_CODEC_ID_PCM_S16LE; + input->format = AV_SAMPLE_FMT_S16; + input->sample_rate = 48000; + av_channel_layout_default(&input->ch_layout, 1); +} +void tearDown(void) { + avcodec_parameters_free(&input); + avcodec_parameters_free(&output); +} +static int probe(void) { + AVRational tb = {1, 48000}; + return transcode_pcm_to_aac(input, &tb, "t7-16", &output); +} +static void expect_synthetic(int channels, int64_t expected) { + synthetic = true; + synthetic_channels = channels; + TEST_ASSERT_EQUAL_INT(0, probe()); + TEST_ASSERT_NOT_NULL(output); + TEST_ASSERT_EQUAL_INT64(expected, output->bit_rate); + TEST_ASSERT_EQUAL_INT(1, encoder_opens); +} +static void test_unsigned_boundary_before_wrap(void) { + expect_synthetic(UINT32_MAX / 64000, INT64_C(4294912000)); +} +static void test_unsigned_boundary_after_wrap(void) { + expect_synthetic(UINT32_MAX / 64000 + 1, INT64_C(4294976000)); +} +static void test_maximum_signed_channel_count(void) { + expect_synthetic(INT_MAX, INT64_C(137438953408000)); +} +static void test_signed_boundary_is_not_sign_extended(void) { + expect_synthetic(INT32_MAX / 64000 + 1, INT64_C(2147520000)); +} +static void test_low_rate_multiplication_also_widens(void) { + input->sample_rate = 8000; + expect_synthetic(UINT32_MAX / 32000 + 1, INT64_C(4294976000)); +} +static void test_synthetic_zero_channels_preserve_mono_fallback(void) { + expect_synthetic(0, 64000); + TEST_ASSERT_EQUAL_UINT32(1, captured.num_channels); +} +static void test_synthetic_negative_channels_preserve_mono_fallback(void) { + expect_synthetic(INT_MIN, 64000); + TEST_ASSERT_EQUAL_UINT32(1, captured.num_channels); +} +static void real_valid(int channels, int rate, int64_t bitrate) { + av_channel_layout_uninit(&input->ch_layout); + av_channel_layout_default(&input->ch_layout, channels); + input->sample_rate = rate; + TEST_ASSERT_EQUAL_INT(0, probe()); + TEST_ASSERT_EQUAL_INT64(bitrate, output->bit_rate); + TEST_ASSERT_EQUAL_INT(channels, output->ch_layout.nb_channels); + TEST_ASSERT_EQUAL_INT(AV_CODEC_ID_AAC, output->codec_id); + TEST_ASSERT_TRUE(output->extradata_size > 0); +} +static void test_real_mono_low_rate(void) { real_valid(1, 8000, 32000); } +static void test_real_stereo(void) { real_valid(2, 48000, 128000); } +static void test_real_maximum_supported_channels(void) { real_valid(8, 48000, 512000); } +static void invalid_channels(int channels) { + av_channel_layout_uninit(&input->ch_layout); + input->ch_layout.order = AV_CHANNEL_ORDER_UNSPEC; + input->ch_layout.nb_channels = channels; + TEST_ASSERT_LESS_THAN_INT(0, probe()); + TEST_ASSERT_NULL(output); +} +static void test_real_zero_channels_rejected(void) { invalid_channels(0); } +static void test_real_negative_channels_rejected(void) { invalid_channels(-1); } +static void test_real_unsupported_channels_rejected(void) { invalid_channels(9); } +static void test_real_huge_channels_rejected(void) { invalid_channels(INT_MAX); } +static void test_real_zero_rate_rejected(void) { + input->sample_rate = 0; + TEST_ASSERT_LESS_THAN_INT(0, probe()); + TEST_ASSERT_NULL(output); +} +static void test_real_negative_rate_rejected(void) { + input->sample_rate = -1; + TEST_ASSERT_LESS_THAN_INT(0, probe()); + TEST_ASSERT_NULL(output); +} +int main(void) { + UNITY_BEGIN(); + RUN_TEST(test_unsigned_boundary_before_wrap); + RUN_TEST(test_unsigned_boundary_after_wrap); + RUN_TEST(test_maximum_signed_channel_count); + RUN_TEST(test_signed_boundary_is_not_sign_extended); + RUN_TEST(test_low_rate_multiplication_also_widens); + RUN_TEST(test_synthetic_zero_channels_preserve_mono_fallback); + RUN_TEST(test_synthetic_negative_channels_preserve_mono_fallback); + RUN_TEST(test_real_mono_low_rate); + RUN_TEST(test_real_stereo); + RUN_TEST(test_real_maximum_supported_channels); + RUN_TEST(test_real_zero_channels_rejected); + RUN_TEST(test_real_negative_channels_rejected); + RUN_TEST(test_real_unsupported_channels_rejected); + RUN_TEST(test_real_huge_channels_rejected); + RUN_TEST(test_real_zero_rate_rejected); + RUN_TEST(test_real_negative_rate_rejected); + return UNITY_END(); +} diff --git a/tests/unit/test_t7_23.c b/tests/unit/test_t7_23.c new file mode 100644 index 000000000..6d5ff7658 --- /dev/null +++ b/tests/unit/test_t7_23.c @@ -0,0 +1,245 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include "unity.h" +#include "database/sqlite_migrate.h" + +static char root[128], directory[192], path[256], outside[256]; +static sqlite3 *db; +static sqlite_migrate_t *migration; +static char *allocated_sql; +static const char ordinary_sql[] = + "-- migrate:up\nCREATE TABLE evidence(value TEXT); INSERT INTO evidence VALUES('kept');\n" + "-- migrate:down\nDROP TABLE evidence;\n"; + +static void write_fixture(const char *sql) { + FILE *file = fopen(path, "w"); TEST_ASSERT_NOT_NULL(file); + size_t count = fwrite(sql, 1, strlen(sql), file); int closed = fclose(file); + TEST_ASSERT_EQUAL_UINT(strlen(sql), count); TEST_ASSERT_EQUAL_INT(0, closed); + TEST_ASSERT_EQUAL_INT(0, chmod(path, 0600)); +} +static int scalar(const char *sql) { + sqlite3_stmt *statement = NULL; + int prepared = sqlite3_prepare_v2(db, sql, -1, &statement, NULL); + int stepped = prepared == SQLITE_OK ? sqlite3_step(statement) : prepared; + int value = stepped == SQLITE_ROW ? sqlite3_column_int(statement, 0) : -1; + if (statement) sqlite3_finalize(statement); + TEST_ASSERT_EQUAL_INT(SQLITE_OK, prepared); TEST_ASSERT_EQUAL_INT(SQLITE_ROW, stepped); + return value; +} +static bool table_exists(const char *name) { + sqlite3_stmt *statement = NULL; + int prepared = sqlite3_prepare_v2(db, "SELECT count(*) FROM sqlite_master WHERE type='table' AND name=?", -1, &statement, NULL); + int bound = prepared == SQLITE_OK ? sqlite3_bind_text(statement, 1, name, -1, SQLITE_TRANSIENT) : prepared; + int stepped = bound == SQLITE_OK ? sqlite3_step(statement) : bound; + int count = stepped == SQLITE_ROW ? sqlite3_column_int(statement, 0) : -1; + if (statement) sqlite3_finalize(statement); + TEST_ASSERT_EQUAL_INT(SQLITE_OK, prepared); TEST_ASSERT_EQUAL_INT(SQLITE_OK, bound); + TEST_ASSERT_EQUAL_INT(SQLITE_ROW, stepped); return count == 1; +} +static void assert_sentinel(void) { + TEST_ASSERT_TRUE(table_exists("sentinel")); + TEST_ASSERT_EQUAL_INT(73, scalar("SELECT value FROM sentinel")); +} +static void open_migrations(const char *tracking_table) { + migrate_config_t config = {.migrations_dir = directory, .migrations_table = tracking_table}; + migration = migrate_init(db, &config); TEST_ASSERT_NOT_NULL(migration); +} +void setUp(void) { + migration = NULL; db = NULL; allocated_sql = NULL; + strcpy(root, "/tmp/lightnvr-t7-23-XXXXXX"); TEST_ASSERT_NOT_NULL(mkdtemp(root)); + snprintf(directory, sizeof(directory), "%s/migrations", root); + snprintf(path, sizeof(path), "%s/0001_identifiers.sql", directory); + snprintf(outside, sizeof(outside), "%s/forbidden.sqlite", root); + TEST_ASSERT_EQUAL_INT(0, mkdir(directory, 0700)); + TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db)); + TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_exec(db, "CREATE TABLE sentinel(value INTEGER); INSERT INTO sentinel VALUES(73);", NULL, NULL, NULL)); +} +void tearDown(void) { + migrate_free(migration); migration = NULL; + sqlite3_free(allocated_sql); allocated_sql = NULL; + if (db) sqlite3_close(db); + db = NULL; + unlink(path); unlink(outside); rmdir(directory); rmdir(root); +} +static void test_valid_tracking_name_applies_reloads_and_rolls_back(void) { + write_fixture(ordinary_sql); open_migrations("custom_tracking"); + TEST_ASSERT_EQUAL_INT(0, migrate_up(migration, NULL)); + TEST_ASSERT_EQUAL_INT(1, scalar("SELECT count(*) FROM custom_tracking")); + migrate_free(migration); migration = NULL; open_migrations("custom_tracking"); + char version[MIGRATE_VERSION_LEN]; TEST_ASSERT_EQUAL_INT(0, migrate_get_version(migration, version, sizeof(version))); + TEST_ASSERT_EQUAL_STRING("0001", version); + TEST_ASSERT_EQUAL_INT(1, migrate_down(migration)); + TEST_ASSERT_FALSE(table_exists("evidence")); + TEST_ASSERT_EQUAL_INT(0, scalar("SELECT count(*) FROM custom_tracking")); assert_sentinel(); +} +static void quoted_identifier_round_trip(const char *name) { + allocated_sql = sqlite3_mprintf("-- migrate:up\nCREATE TABLE \"%w\"(value TEXT);\n" + "-- migrate:down\nDROP TABLE \"%w\";\n", name, name); + TEST_ASSERT_NOT_NULL(allocated_sql); + /* SQLite itself accepts this quoted identifier; this is not malformed SQL. */ + char *control = sqlite3_mprintf("CREATE TABLE \"%w\"(value TEXT);", name); + TEST_ASSERT_NOT_NULL(control); + sqlite3_stmt *statement = NULL; + int prepared = sqlite3_prepare_v2(db, control, -1, &statement, NULL); + if (statement) sqlite3_finalize(statement); + sqlite3_free(control); + TEST_ASSERT_EQUAL_INT(SQLITE_OK, prepared); + write_fixture(allocated_sql); open_migrations(NULL); + int applied = migrate_up(migration, NULL); assert_sentinel(); + TEST_ASSERT_EQUAL_INT(0, applied); TEST_ASSERT_TRUE(table_exists(name)); + TEST_ASSERT_EQUAL_INT(1, migrate_down(migration)); + TEST_ASSERT_FALSE(table_exists(name)); assert_sentinel(); +} +static void test_double_quote_in_identifier_round_trips(void) { quoted_identifier_round_trip("quoted\"identifier"); } +static void test_apostrophe_in_quoted_identifier_round_trips(void) { quoted_identifier_round_trip("quoted'identifier"); } +static void test_semicolon_and_comment_text_in_quoted_identifier_round_trip(void) { + quoted_identifier_round_trip("semi;--/*identifier*/"); +} +static void statement_round_trip(const char *up, const char *down) { + allocated_sql = sqlite3_mprintf("-- migrate:up\n%s\n-- migrate:down\n%s\n", up, down); + TEST_ASSERT_NOT_NULL(allocated_sql); + write_fixture(allocated_sql); open_migrations(NULL); + TEST_ASSERT_EQUAL_INT(0, migrate_up(migration, NULL)); assert_sentinel(); + TEST_ASSERT_EQUAL_INT(1, migrate_down(migration)); assert_sentinel(); +} +static void test_backtick_identifier_and_escaped_delimiter_round_trip(void) { + statement_round_trip("CREATE TABLE `semi;quo``te'name`(value TEXT);", + "DROP TABLE `semi;quo``te'name`;"); +} +static void test_bracket_identifier_round_trip(void) { + statement_round_trip("CREATE TABLE [semi;quote'name](value TEXT);", + "DROP TABLE [semi;quote'name];"); +} +static void test_inline_comments_do_not_change_statement_boundaries(void) { + statement_round_trip("CREATE TABLE evidence /* '; END */ (value TEXT);\n" + "INSERT INTO evidence VALUES('it''s; kept'); -- '; BEGIN\n" + "UPDATE evidence SET value='changed';", + "DROP TABLE evidence /* '; BEGIN */;"); +} +static void test_trigger_quoted_keywords_and_nested_case_round_trip(void) { + statement_round_trip("CREATE TABLE evidence(value TEXT);" + "CREATE TRIGGER \"BEGIN;END\" AFTER INSERT ON evidence BEGIN " + "UPDATE evidence SET value=CASE WHEN new.value='it''s;' THEN 'END' ELSE 'BEGIN' END; END;" + "INSERT INTO evidence VALUES('it''s;');", + "DROP TRIGGER \"BEGIN;END\"; DROP TABLE evidence;"); +} +static void trigger_following_statement_is_validated(const char *name) { + allocated_sql = sqlite3_mprintf("-- migrate:up\nCREATE TABLE evidence(value TEXT);" + "CREATE TRIGGER %s AFTER INSERT ON evidence BEGIN SELECT 1; END;" + "ATTACH DATABASE '%q' AS escaped;\n-- migrate:down\nDROP TABLE evidence;\n", name, outside); + TEST_ASSERT_NOT_NULL(allocated_sql); write_fixture(allocated_sql); open_migrations(NULL); + int result = migrate_up(migration, NULL); + assert_sentinel(); TEST_ASSERT_EQUAL_INT(-1, access(outside, F_OK)); + TEST_ASSERT_EQUAL_INT(-1, result); TEST_ASSERT_FALSE(table_exists("evidence")); + TEST_ASSERT_EQUAL_INT(0, scalar("SELECT count(*) FROM schema_migrations")); +} +static void test_trigger_named_begin_does_not_hide_following_statement(void) { + trigger_following_statement_is_validated("BEGIN"); +} +static void test_trigger_dollar_identifier_does_not_hide_following_statement(void) { + trigger_following_statement_is_validated("BEGIN$name"); +} +static void test_trigger_unicode_identifier_does_not_hide_following_statement(void) { + trigger_following_statement_is_validated("BEGIN\xc3\xa9"); +} +static void test_trigger_keyword_name_and_terminator_comments_round_trip(void) { + statement_round_trip("CREATE TABLE evidence(value TEXT);" + "CREATE TRIGGER BEGIN AFTER INSERT ON evidence BEGIN " + "SELECT CASE WHEN 1 THEN 'END;' ELSE ';END;' END; /* ; END */ END /* ; */;" + "INSERT INTO evidence VALUES('kept');", + "DROP TRIGGER BEGIN; DROP TABLE evidence;"); +} +static void test_up_identifier_quote_cannot_hide_disallowed_attach(void) { + char sql[1024]; + int length = snprintf(sql, sizeof(sql), "-- migrate:up\n" + "CREATE TABLE \"quoted'name\"(value TEXT); ATTACH DATABASE '%s' AS escaped;\n" + "-- migrate:down\nDROP TABLE \"quoted'name\";\n", outside); + TEST_ASSERT_TRUE(length > 0 && (size_t)length < sizeof(sql)); + write_fixture(sql); open_migrations(NULL); + int applied = migrate_up(migration, NULL); + assert_sentinel(); TEST_ASSERT_EQUAL_INT(-1, access(outside, F_OK)); + TEST_ASSERT_EQUAL_INT(1, scalar("SELECT count(*) FROM pragma_database_list")); + TEST_ASSERT_EQUAL_INT(-1, applied); TEST_ASSERT_FALSE(table_exists("quoted'name")); + TEST_ASSERT_EQUAL_INT(0, scalar("SELECT count(*) FROM schema_migrations")); +} +static void test_down_identifier_quote_cannot_hide_disallowed_attach(void) { + char sql[1024]; + int length = snprintf(sql, sizeof(sql), "-- migrate:up\nCREATE TABLE \"quoted'name\"(value TEXT);\n" + "-- migrate:down\nDROP TABLE \"quoted'name\"; ATTACH DATABASE '%s' AS escaped;\n", outside); + TEST_ASSERT_TRUE(length > 0 && (size_t)length < sizeof(sql)); + write_fixture(sql); open_migrations(NULL); + TEST_ASSERT_EQUAL_INT(0, migrate_up(migration, NULL)); + int rolled_back = migrate_down(migration); + assert_sentinel(); TEST_ASSERT_EQUAL_INT(-1, access(outside, F_OK)); + TEST_ASSERT_EQUAL_INT(1, scalar("SELECT count(*) FROM pragma_database_list")); + TEST_ASSERT_EQUAL_INT(-1, rolled_back); TEST_ASSERT_TRUE(table_exists("quoted'name")); + TEST_ASSERT_EQUAL_INT(1, scalar("SELECT count(*) FROM schema_migrations")); +} +static void test_tracking_identifier_cannot_execute_second_statement(void) { + migrate_config_t config = {.migrations_table = "injected(version TEXT); DROP TABLE sentinel; --"}; + migration = migrate_init(db, &config); + /* Either literal treatment or rejection is safe; execution of fragments is not. */ + assert_sentinel(); TEST_ASSERT_FALSE(table_exists("injected")); +} +static void test_tracking_quote_and_comment_names_cannot_change_unrelated_schema(void) { + const char *names[] = {"quote\"tracking", "tracking;--comment", "tracking/*comment*/"}; + for (size_t index = 0; index < sizeof(names)/sizeof(names[0]); ++index) { + migrate_config_t config = {.migrations_table = names[index]}; + migration = migrate_init(db, &config); assert_sentinel(); + migrate_free(migration); migration = NULL; + } +} +static void tracking_round_trip(const char *name) { + write_fixture(ordinary_sql); open_migrations(name); + TEST_ASSERT_TRUE(table_exists(name)); + TEST_ASSERT_EQUAL_INT(0, migrate_up(migration, NULL)); assert_sentinel(); + migrate_free(migration); migration = NULL; open_migrations(name); + char version[MIGRATE_VERSION_LEN]; + TEST_ASSERT_EQUAL_INT(0, migrate_get_version(migration, version, sizeof(version))); + TEST_ASSERT_EQUAL_STRING("0001", version); + TEST_ASSERT_EQUAL_INT(1, migrate_down(migration)); assert_sentinel(); + TEST_ASSERT_FALSE(table_exists("evidence")); +} +static void test_quoted_tracking_name_round_trips_as_one_identifier(void) { + tracking_round_trip("track\"ing; -- /*name*/ 'quoted'"); +} +static void test_long_tracking_name_round_trips_without_truncation(void) { + char name[1025]; memset(name, 'x', sizeof(name) - 1); name[sizeof(name) - 1] = '\0'; + tracking_round_trip(name); +} +static void test_migration_version_is_bound_as_data(void) { + migration_t embedded = {.version = "v'; DROP TABLE sentinel;--", .description = "bound value", + .sql_up = "CREATE TABLE evidence(value TEXT);", .sql_down = "DROP TABLE evidence;"}; + migrate_config_t config = {.embedded_migrations = &embedded, .embedded_count = 1}; + migration = migrate_init(db, &config); TEST_ASSERT_NOT_NULL(migration); + TEST_ASSERT_EQUAL_INT(0, migrate_up(migration, NULL)); assert_sentinel(); + TEST_ASSERT_EQUAL_INT(1, migrate_down(migration)); assert_sentinel(); +} +int main(void) { + UNITY_BEGIN(); + RUN_TEST(test_valid_tracking_name_applies_reloads_and_rolls_back); + RUN_TEST(test_double_quote_in_identifier_round_trips); + RUN_TEST(test_apostrophe_in_quoted_identifier_round_trips); + RUN_TEST(test_semicolon_and_comment_text_in_quoted_identifier_round_trip); + RUN_TEST(test_backtick_identifier_and_escaped_delimiter_round_trip); + RUN_TEST(test_bracket_identifier_round_trip); + RUN_TEST(test_inline_comments_do_not_change_statement_boundaries); + RUN_TEST(test_trigger_quoted_keywords_and_nested_case_round_trip); + RUN_TEST(test_trigger_named_begin_does_not_hide_following_statement); + RUN_TEST(test_trigger_dollar_identifier_does_not_hide_following_statement); + RUN_TEST(test_trigger_unicode_identifier_does_not_hide_following_statement); + RUN_TEST(test_trigger_keyword_name_and_terminator_comments_round_trip); + RUN_TEST(test_up_identifier_quote_cannot_hide_disallowed_attach); + RUN_TEST(test_down_identifier_quote_cannot_hide_disallowed_attach); + RUN_TEST(test_tracking_identifier_cannot_execute_second_statement); + RUN_TEST(test_tracking_quote_and_comment_names_cannot_change_unrelated_schema); + RUN_TEST(test_quoted_tracking_name_round_trips_as_one_identifier); + RUN_TEST(test_long_tracking_name_round_trips_without_truncation); + RUN_TEST(test_migration_version_is_bound_as_data); + return UNITY_END(); +} diff --git a/tests/unit/test_t7_24.c b/tests/unit/test_t7_24.c new file mode 100644 index 000000000..3c04e11e3 --- /dev/null +++ b/tests/unit/test_t7_24.c @@ -0,0 +1,288 @@ +/* CODEQL-330 verification producer. Real pinned FAAC, no replacement codec. + * Passing accesses do not prove final unused pointer formation is valid C. */ +#include +#include +#include +#include +#include "unity.h" +#include "faac.h" + +static faac_encoder *enc[2]; +static faac_encoder_info info[2]; +static void *owned[32]; + +void setUp(void) { + memset(enc, 0, sizeof(enc)); + memset(info, 0, sizeof(info)); + memset(owned, 0, sizeof(owned)); +} +void tearDown(void) { + int failures = 0; + for (unsigned i = 0; i < 2; ++i) { + if (enc[i] && faac_encoder_close(&enc[i]) != FAAC_OK) ++failures; + if (enc[i]) ++failures; + } + for (unsigned i = 0; i < 32; ++i) { free(owned[i]); owned[i] = NULL; } + TEST_ASSERT_EQUAL_INT_MESSAGE(0, failures, "all owned encoders must close and null their handle"); +} +static void *take(size_t n) { + for (unsigned i = 0; i < 32; ++i) if (!owned[i]) { + owned[i] = malloc(n); + TEST_ASSERT_NOT_NULL(owned[i]); + return owned[i]; + } + TEST_FAIL_MESSAGE("fixture allocation registry exhausted"); + return NULL; +} +static void release(void *p) { + for (unsigned i = 0; i < 32; ++i) if (owned[i] == p) { + free(p); owned[i] = NULL; return; + } + TEST_FAIL_MESSAGE("fixture release must own allocation"); +} +static faac_params params(uint32_t channels, enum faac_input_format format) { + faac_params p; + TEST_ASSERT_EQUAL_INT(FAAC_OK, faac_params_init(&p, sizeof(p))); + p.sample_rate = 48000; + p.num_channels = channels; + p.object_type = FAAC_OBJ_LOW; + p.input_format = format; + p.output_format = FAAC_STREAM_RAW; + p.bit_rate = 64000; + p.joint_mode = FAAC_JOINT_NONE; + return p; +} +static void open_one(unsigned slot, faac_params *p) { + TEST_ASSERT_EQUAL_INT(FAAC_OK, faac_encoder_open(p, &enc[slot])); + TEST_ASSERT_NOT_NULL(enc[slot]); + info[slot].struct_size = sizeof(info[slot]); + TEST_ASSERT_EQUAL_INT(FAAC_OK, faac_encoder_get_info(enc[slot], &info[slot])); + TEST_ASSERT_EQUAL_UINT32(1024, info[slot].frame_samples); + TEST_ASSERT_GREATER_THAN_UINT32(0, info[slot].max_output_bytes); +} +static int16_t sample(uint32_t frame, uint32_t channel) { + return (int16_t)((int32_t)((frame * 137u + channel * 4093u) % 60001u) - 30000); +} +static void compare_call(const void *a, const void *b, uint32_t n, + uint8_t *out0, uint8_t *out1, uint64_t *total) { + uint32_t bytes0 = UINT32_MAX, bytes1 = UINT32_MAX; + TEST_ASSERT_EQUAL_INT(FAAC_OK, faac_encoder_encode(enc[0], a, n, out0, + info[0].max_output_bytes, &bytes0)); + TEST_ASSERT_EQUAL_INT(FAAC_OK, faac_encoder_encode(enc[1], b, n, out1, + info[1].max_output_bytes, &bytes1)); + TEST_ASSERT_LESS_OR_EQUAL_UINT32(info[0].max_output_bytes, bytes0); + TEST_ASSERT_EQUAL_UINT32(bytes0, bytes1); + if (bytes0) TEST_ASSERT_EQUAL_MEMORY(out0, out1, bytes0); + *total += bytes0; +} +static void layout(uint32_t channels, int reversed, size_t offset) { + int32_t map[8]; + for (uint32_t c = 0; c < channels; ++c) map[c] = (int32_t)(channels - 1 - c); + faac_params p0 = params(channels, FAAC_INPUT_16BIT); + faac_params p1 = params(channels, FAAC_INPUT_32BIT); + if (reversed) { p0.channel_map = map; p0.channel_map_count = channels; } + open_one(0, &p0); open_one(1, &p1); + /* Map is copied at open; later caller mutation must not change selection. */ + memset(map, 0xff, sizeof(map)); + uint8_t *out0 = take(info[0].max_output_bytes); + uint8_t *out1 = take(info[1].max_output_bytes); + const uint32_t groups[] = {1, 511, 512, 1024, 1024, 1024}; + uint32_t frame = 0; + uint64_t total = 0; + for (unsigned call = 0; call < sizeof(groups)/sizeof(groups[0]); ++call) { + uint32_t n = groups[call] * channels; + size_t bytes = (size_t)n * sizeof(int16_t); + /* Exact suffix allocation: last declared sample abuts ASan redzone. */ + uint8_t *raw = take(bytes + offset); + int16_t *pcm = (int16_t *)(raw + offset); + int32_t *wide = take((size_t)n * sizeof(*wide)); + uint8_t *before = take(bytes + offset); + memset(raw, 0xa5, offset); + if (offset) { + TEST_ASSERT_EQUAL_UINT32(0, (uintptr_t)pcm % _Alignof(int16_t)); + TEST_ASSERT_NOT_EQUAL(0, (uintptr_t)pcm % _Alignof(int32_t)); + } + for (uint32_t i = 0; i < groups[call]; ++i) { + for (uint32_t c = 0; c < channels; ++c) { + pcm[i * channels + c] = sample(frame + i, c); + uint32_t source = reversed ? channels - 1 - c : c; + wide[i * channels + c] = (int32_t)sample(frame + i, source) * 256; + } + } + memcpy(before, raw, bytes + offset); + compare_call(pcm, wide, n, out0, out1, &total); + TEST_ASSERT_EQUAL_MEMORY(before, raw, bytes + offset); + for (uint32_t i = 0; i < groups[call]; ++i) + for (uint32_t c = 0; c < channels; ++c) + TEST_ASSERT_EQUAL_INT32((int32_t)sample(frame + i, + reversed ? channels - 1 - c : c) * 256, wide[i * channels + c]); + frame += groups[call]; + release(before); release(wide); release(raw); + } + /* Draining both independent instances must produce equal nonempty audio. */ + int drained = 0; + for (unsigned i = 0; i < 16; ++i) { + uint64_t prior = total; + compare_call(NULL, NULL, 0, out0, out1, &total); + if (total == prior) { drained = 1; break; } + } + TEST_ASSERT_TRUE_MESSAGE(drained, "flush must finish within 16 calls"); + TEST_ASSERT_GREATER_THAN_UINT64(0, total); + for (unsigned i = 0; i < 2; ++i) { + TEST_ASSERT_EQUAL_INT(FAAC_OK, faac_encoder_close(&enc[i])); + TEST_ASSERT_NULL(enc[i]); + } +} +static void test_mono_packed_16_matches_32(void) { layout(1, 0, 0); } +static void test_stereo_packed_16_matches_32(void) { layout(2, 0, 0); } +static void test_eight_channels_packed_16_matches_32(void) { layout(8, 0, 0); } +static void test_stereo_reverse_map_matches_reordered_input(void) { layout(2, 1, 0); } +static void test_eight_channel_reverse_map_matches_reordered_input(void) { layout(8, 1, 0); } +static void test_stereo_int16_only_alignment(void) { layout(2, 0, sizeof(int16_t)); } +static void test_eight_channel_int16_only_alignment(void) { layout(8, 1, sizeof(int16_t)); } +static void test_invalid_channel_maps_rejected_without_handle(void) { + faac_params p = params(2, FAAC_INPUT_16BIT); + int32_t map[2] = {-1, 1}; + p.channel_map = map; p.channel_map_count = 2; + TEST_ASSERT_EQUAL_INT(FAAC_ERR_INVALID_ARGUMENT, faac_encoder_open(&p, &enc[0])); + TEST_ASSERT_NULL(enc[0]); + map[0] = 2; + TEST_ASSERT_EQUAL_INT(FAAC_ERR_INVALID_ARGUMENT, faac_encoder_open(&p, &enc[0])); + TEST_ASSERT_NULL(enc[0]); + map[0] = 0; p.channel_map_count = 1; + TEST_ASSERT_EQUAL_INT(FAAC_ERR_INVALID_ARGUMENT, faac_encoder_open(&p, &enc[0])); + TEST_ASSERT_NULL(enc[0]); +} +static void test_rejected_arguments_do_not_consume_input(void) { + faac_params p = params(2, FAAC_INPUT_16BIT); + open_one(0, &p); open_one(1, &p); + uint32_t n = info[0].frame_samples * 2; + int16_t *pcm = take((size_t)n * sizeof(*pcm)); + for (uint32_t i = 0; i < n; ++i) pcm[i] = sample(i / 2, i % 2); + uint8_t *out0 = take(info[0].max_output_bytes); + uint8_t *out1 = take(info[1].max_output_bytes); + memset(out0, 0x6d, info[0].max_output_bytes); + uint32_t count = 99; + TEST_ASSERT_EQUAL_INT(FAAC_ERR_INVALID_ARGUMENT, + faac_encoder_encode(enc[0], NULL, 2, out0, info[0].max_output_bytes, &count)); + TEST_ASSERT_EQUAL_UINT32(0, count); + count = 99; + /* Declared over-limit count must be rejected before touching this buffer. */ + TEST_ASSERT_EQUAL_INT(FAAC_ERR_INPUT_OVERFLOW, + faac_encoder_encode(enc[0], pcm, n + 2, out0, info[0].max_output_bytes, &count)); + TEST_ASSERT_EQUAL_UINT32(0, count); + uint8_t *tiny = take(1); tiny[0] = 0x47; count = 99; + TEST_ASSERT_EQUAL_INT(FAAC_ERR_OUTPUT_TOO_SMALL, + faac_encoder_encode(enc[0], pcm, n, tiny, 1, &count)); + TEST_ASSERT_EQUAL_UINT32(0, count); + TEST_ASSERT_EQUAL_HEX8(0x47, tiny[0]); + TEST_ASSERT_EQUAL_INT(FAAC_ERR_INVALID_ARGUMENT, + faac_encoder_encode(enc[0], pcm, n, NULL, info[0].max_output_bytes, &count)); + TEST_ASSERT_EQUAL_INT(FAAC_ERR_INVALID_ARGUMENT, + faac_encoder_encode(enc[0], pcm, n, out0, info[0].max_output_bytes, NULL)); + for (uint32_t i = 0; i < info[0].max_output_bytes; ++i) TEST_ASSERT_EQUAL_HEX8(0x6d, out0[i]); + uint64_t total = 0; + for (unsigned i = 0; i < 4; ++i) compare_call(pcm, pcm, n, out0, out1, &total); + int drained = 0; + for (unsigned i = 0; i < 16; ++i) { + uint64_t old = total; + compare_call(NULL, NULL, 0, out0, out1, &total); + if (old == total) { drained = 1; break; } + } + TEST_ASSERT_TRUE(drained); + TEST_ASSERT_GREATER_THAN_UINT64(0, total); + for (uint32_t i = 0; i < n; ++i) TEST_ASSERT_EQUAL_INT16(sample(i / 2, i % 2), pcm[i]); +} +static void test_empty_flush_without_input(void) { + faac_params p = params(2, FAAC_INPUT_16BIT); + open_one(0, &p); + uint8_t *out = take(info[0].max_output_bytes); + /* Even an empty encoder may emit priming frames. The public contract + * requires repeated drain until zero, not zero on its first flush. */ + int drained = 0; + for (unsigned i = 0; i < 16; ++i) { + uint32_t bytes = UINT32_MAX; + TEST_ASSERT_EQUAL_INT(FAAC_OK, + faac_encoder_encode(enc[0], NULL, 0, out, info[0].max_output_bytes, &bytes)); + TEST_ASSERT_LESS_OR_EQUAL_UINT32(info[0].max_output_bytes, bytes); + if (bytes == 0) { drained = 1; break; } + } + TEST_ASSERT_TRUE_MESSAGE(drained, "empty encoder flush must terminate within 16 calls"); +} +/* Additional shared-transport format controls for the tracked correction. */ +static void additional_format(enum faac_input_format format, uint32_t channels) { + faac_params a = params(channels, format); + faac_params b = params(channels, FAAC_INPUT_32BIT); + open_one(0, &a); open_one(1, &b); + uint8_t *out0 = take(info[0].max_output_bytes); + uint8_t *out1 = take(info[1].max_output_bytes); + const uint32_t chunks[] = {1, 511, 512, 1024, 1024, 1024}; + uint16_t endian = 1; + int little = *(const uint8_t *)&endian; + uint32_t frame = 0; + uint64_t total = 0; + for (unsigned call = 0; call < sizeof(chunks)/sizeof(chunks[0]); ++call) { + uint32_t n = chunks[call] * channels; + size_t width = format == FAAC_INPUT_24BIT ? 3 : sizeof(float); + size_t offset = format == FAAC_INPUT_24BIT ? 1 : 0; + size_t size = n * width + offset; + uint8_t *raw = take(size), *before = take(size); + int32_t *wide = take(n * sizeof(*wide)); + raw[0] = 0x57; + for (uint32_t i = 0; i < n; ++i) { + int32_t value = sample(frame + i / channels, i % channels); + if (format == FAAC_INPUT_FLOAT) { + float f = (float)value; + switch ((frame + i) % 257) { + case 0: f = NAN; value = 0; break; + case 1: f = INFINITY; value = 0; break; + case 2: f = -INFINITY; value = 0; break; + case 3: f = 8388608.0f; value = 0; break; + case 4: f = -8388608.0f; value = 0; break; + default: break; + } + ((float *)raw)[i] = f; + } else { + uint32_t bits = (uint32_t)(value * 256); + for (unsigned byte = 0; byte < 3; ++byte) + raw[offset + 3 * i + byte] = (uint8_t)(bits >> (8 * (little ? byte : 2 - byte))); + } + wide[i] = value * 256; + } + memcpy(before, raw, size); + compare_call(raw + offset, wide, n, out0, out1, &total); + TEST_ASSERT_EQUAL_MEMORY(before, raw, size); + frame += chunks[call]; + release(wide); release(before); release(raw); + } + int drained = 0; + for (unsigned i = 0; i < 16; ++i) { + uint64_t old = total; + compare_call(NULL, NULL, 0, out0, out1, &total); + if (old == total) { drained = 1; break; } + } + TEST_ASSERT_TRUE(drained); + TEST_ASSERT_GREATER_THAN_UINT64(0, total); +} +static void test_packed24_stereo_byte_aligned(void) { additional_format(FAAC_INPUT_24BIT, 2); } +static void test_packed24_eight_channels_byte_aligned(void) { additional_format(FAAC_INPUT_24BIT, 8); } +static void test_float_stereo_preserves_screening(void) { additional_format(FAAC_INPUT_FLOAT, 2); } +static void test_float_eight_channels_preserves_screening(void) { additional_format(FAAC_INPUT_FLOAT, 8); } +int main(void) { + UNITY_BEGIN(); + RUN_TEST(test_mono_packed_16_matches_32); + RUN_TEST(test_stereo_packed_16_matches_32); + RUN_TEST(test_eight_channels_packed_16_matches_32); + RUN_TEST(test_stereo_reverse_map_matches_reordered_input); + RUN_TEST(test_eight_channel_reverse_map_matches_reordered_input); + RUN_TEST(test_stereo_int16_only_alignment); + RUN_TEST(test_eight_channel_int16_only_alignment); + RUN_TEST(test_invalid_channel_maps_rejected_without_handle); + RUN_TEST(test_rejected_arguments_do_not_consume_input); + RUN_TEST(test_empty_flush_without_input); + RUN_TEST(test_packed24_stereo_byte_aligned); + RUN_TEST(test_packed24_eight_channels_byte_aligned); + RUN_TEST(test_float_stereo_preserves_screening); + RUN_TEST(test_float_eight_channels_preserves_screening); + return UNITY_END(); +} diff --git a/tests/unit/test_t7_25.c b/tests/unit/test_t7_25.c new file mode 100644 index 000000000..8a4174e3f --- /dev/null +++ b/tests/unit/test_t7_25.c @@ -0,0 +1,411 @@ +#define _GNU_SOURCE +#define _POSIX_C_SOURCE 200809L +/* Actual private cleanup helper, with public DB definitions linked from here. */ +#include "../../src/database/db_core.c" +#include "unity.h" + +static char root[128], backup_dir[256], moved_dir[256], artifact[320], sentinel[256]; +static bool armed, replaced, leaf_swap, regular_swap, fixture_config_loaded; +static sqlite3 *fixture_db; +static config_t saved_config; +static struct stat original_identity; +static bool held_inode_verified, fail_candidate_open, fail_private_open, fail_mkdir; +static bool fail_move, fail_delete, restore_collision, change_after_move; +static int restore_error, move_error, private_stat_fault, private_fd; +static unsigned restore_calls, mkdir_calls; +static bool fail_uuid, collide_mkdir, repeat_restored, repeated, substitute_private_directory; +struct dirent *__real_readdir(DIR *); +struct dirent *__wrap_readdir(DIR *dir) { + if (repeat_restored && restore_calls && !repeated) { + static struct dirent duplicate; + memset(&duplicate, 0, sizeof(duplicate)); + strcpy(duplicate.d_name, "20261031T010000Z.sqlite3.tmp"); + repeated = true; + return &duplicate; + } + return __real_readdir(dir); +} +int __real_lightnvr_uuid_generate_v4(char *); +int __wrap_lightnvr_uuid_generate_v4(char *uuid) { + return fail_uuid ? -1 : __real_lightnvr_uuid_generate_v4(uuid); +} +static int owned_fds[32]; +static unsigned owned_fd_count; +static const char artifact_name[] = "20261031T010000Z.sqlite3.tmp"; +static void put_file(const char *path, const char *bytes) { + FILE *file = fopen(path, "w"); TEST_ASSERT_NOT_NULL(file); + TEST_ASSERT_EQUAL_UINT(strlen(bytes), fwrite(bytes, 1, strlen(bytes), file)); + TEST_ASSERT_EQUAL_INT(0, fclose(file)); +} +static void replace_after_check(void) { + armed = false; replaced = true; + if (leaf_swap) { + TEST_ASSERT_EQUAL_INT(0, unlink(artifact)); + if (regular_swap) put_file(artifact, "new regular file must survive"); + else TEST_ASSERT_EQUAL_INT(0, symlink(sentinel, artifact)); + } else { + TEST_ASSERT_EQUAL_INT(0, rename(backup_dir, moved_dir)); + TEST_ASSERT_EQUAL_INT(0, mkdir(backup_dir, 0700)); + put_file(artifact, "replacement must survive"); + } +} +int __real_openat(int, const char *, int, ...); +int __wrap_openat(int fd, const char *name, int flags, ...) { + mode_t mode = 0; + if (flags & O_CREAT) { va_list args; va_start(args, flags); mode = va_arg(args, int); va_end(args); } + bool candidate = strcmp(name, artifact_name) == 0; + bool private = strncmp(name, ".cleanup-", 9) == 0; + if ((candidate && fail_candidate_open) || (private && fail_private_open)) { errno = EACCES; return -1; } + int result = __real_openat(fd, name, flags, mode); + if (result >= 0) { + TEST_ASSERT_LESS_THAN_UINT(32, owned_fd_count); + owned_fds[owned_fd_count++] = result; + if (private) private_fd = result; + } + return result; +} +int __real_close(int); +int __wrap_close(int fd) { + int result = __real_close(fd); + for (unsigned i = 0; i < owned_fd_count; i++) if (owned_fds[i] == fd) { + owned_fds[i] = owned_fds[--owned_fd_count]; break; + } + return result; +} +int __real_fstat(int, struct stat *); +int __wrap_fstat(int fd, struct stat *info) { + int result = __real_fstat(fd, info); + if (result == 0 && armed && info->st_dev == original_identity.st_dev && + info->st_ino == original_identity.st_ino && S_ISREG(info->st_mode)) { + held_inode_verified = true; + replace_after_check(); + struct stat pinned; + TEST_ASSERT_EQUAL_INT(0, __real_fstat(fd, &pinned)); + TEST_ASSERT_EQUAL_UINT64(original_identity.st_ino, pinned.st_ino); + } + if (result == 0 && fd == private_fd && S_ISDIR(info->st_mode)) { + if (private_stat_fault == 1) info->st_uid = geteuid() + 1; + if (private_stat_fault == 2) info->st_mode |= 0070; + if (private_stat_fault == 3) info->st_ino++; + } + return result; +} +int __real_mkdirat(int, const char *, mode_t); +int __wrap_mkdirat(int fd, const char *name, mode_t mode) { + if (strncmp(name, ".cleanup-", 9) == 0) { + mkdir_calls++; + if (fail_mkdir || collide_mkdir) { errno = collide_mkdir ? EEXIST : EACCES; return -1; } + } + int result = __real_mkdirat(fd, name, mode); + if (result == 0 && substitute_private_directory && strncmp(name, ".cleanup-", 9) == 0) { + char prepared[256]; snprintf(prepared, sizeof(prepared), "%s/prepared-private", root); + TEST_ASSERT_EQUAL_INT(0, renameat(fd, name, fd, "saved-created-directory")); + TEST_ASSERT_EQUAL_INT(0, renameat(AT_FDCWD, prepared, fd, name)); + } + return result; +} +long __real_syscall(long, ...); +long __wrap_syscall(long number, ...) { + if (number != SYS_renameat2) { + TEST_FAIL_MESSAGE("Unexpected syscall in bounded renameat2 fault seam"); + errno = ENOSYS; return -1; + } + va_list args; va_start(args, number); + int oldfd = va_arg(args, int); + const char *oldname = va_arg(args, const char *); + int newfd = va_arg(args, int); + const char *newname = va_arg(args, const char *); + unsigned int flags = va_arg(args, unsigned int); + va_end(args); + TEST_ASSERT_EQUAL_UINT(RENAME_NOREPLACE, flags); + if (newfd == private_fd) { + if (fail_move || move_error) { errno = move_error ? move_error : EIO; return -1; } + long result = __real_syscall(number, oldfd, oldname, newfd, newname, flags); + if (result == 0) { + if (restore_collision) put_file(artifact, "concurrent destination must survive"); + if (change_after_move) { + int fd = __real_openat(newfd, newname, O_WRONLY | O_APPEND); + TEST_ASSERT_GREATER_OR_EQUAL_INT(0, fd); + TEST_ASSERT_EQUAL_INT(8, write(fd, " changed", 8)); + TEST_ASSERT_EQUAL_INT(0, close(fd)); + } + } + return result; + } + restore_calls++; + if (restore_error) { errno = restore_error; return -1; } + return __real_syscall(number, oldfd, oldname, newfd, newname, flags); +} +int __real_unlinkat(int, const char *, int); +int __wrap_unlinkat(int fd, const char *name, int flags) { + if (fail_delete && flags == 0 && strcmp(name, artifact_name) == 0) { errno = EACCES; return -1; } + return __real_unlinkat(fd, name, flags); +} +int __real_lstat(const char *, struct stat *); +int __wrap_lstat(const char *path, struct stat *info) { + int result = __real_lstat(path, info); + if (armed && result == 0 && strcmp(path, artifact) == 0) replace_after_check(); + return result; +} +int __real_fstatat(int, const char *, struct stat *, int); +int __wrap_fstatat(int fd, const char *name, struct stat *info, int flags) { + int result = __real_fstatat(fd, name, info, flags); + if (armed && result == 0 && strcmp(name, artifact_name) == 0) replace_after_check(); + return result; +} +static void assert_bytes(const char *path, const char *expected) { + FILE *file = fopen(path, "r"); TEST_ASSERT_NOT_NULL(file); + char actual[128] = {0}; size_t bytes = fread(actual, 1, sizeof(actual)-1, file); + TEST_ASSERT_EQUAL_INT(0, fclose(file)); + TEST_ASSERT_EQUAL_UINT(strlen(expected), bytes); TEST_ASSERT_EQUAL_STRING(expected, actual); +} +static void remove_owned_tree(const char *directory) { + DIR *dir = opendir(directory); if (!dir) return; + struct dirent *entry; + while ((entry = readdir(dir))) { + if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue; + char path[512]; snprintf(path, sizeof(path), "%s/%s", directory, entry->d_name); + struct stat info; + if (lstat(path, &info) == 0 && S_ISDIR(info.st_mode)) remove_owned_tree(path); + else unlink(path); + } + closedir(dir); rmdir(directory); +} +void setUp(void) { + saved_config = g_config; fixture_db = NULL; fixture_config_loaded = false; + strcpy(root, "/tmp/lightnvr-t7-25-XXXXXX"); TEST_ASSERT_NOT_NULL(mkdtemp(root)); + snprintf(backup_dir, sizeof(backup_dir), "%s/backup", root); + snprintf(moved_dir, sizeof(moved_dir), "%s/inspected", root); + snprintf(artifact, sizeof(artifact), "%s/%s", backup_dir, artifact_name); + snprintf(sentinel, sizeof(sentinel), "%s/sentinel", root); + TEST_ASSERT_EQUAL_INT(0, mkdir(backup_dir, 0700)); + put_file(artifact, "stale partial backup"); put_file(sentinel, "sentinel unchanged"); + armed = replaced = leaf_swap = regular_swap = false; + held_inode_verified = fail_candidate_open = fail_private_open = fail_mkdir = false; + fail_move = fail_delete = restore_collision = change_after_move = false; + restore_error = move_error = private_stat_fault = 0; private_fd = -1; + restore_calls = owned_fd_count = mkdir_calls = 0; + fail_uuid = collide_mkdir = repeat_restored = repeated = substitute_private_directory = false; + TEST_ASSERT_EQUAL_INT(0, stat(artifact, &original_identity)); +} +void tearDown(void) { + armed = repeat_restored = false; + if (get_db_handle()) shutdown_database(); + if (fixture_db) { sqlite3_close_v2(fixture_db); fixture_db = NULL; } + if (fixture_config_loaded) free(g_config.streams); + g_config = saved_config; + remove_owned_tree(root); + TEST_ASSERT_EQUAL_INT(-1, access(root, F_OK)); + TEST_ASSERT_EQUAL_UINT(0, owned_fd_count); +} +static void test_normal_stale_cleanup_preserves_completed_unrelated_and_symlinks(void) { + char complete[320], other[320], link[320], directory[320]; + snprintf(complete, sizeof(complete), "%s/complete.sqlite3", backup_dir); + snprintf(other, sizeof(other), "%s/operator-notes.tmp", backup_dir); + snprintf(link, sizeof(link), "%s/link.sqlite3.tmp", backup_dir); + snprintf(directory, sizeof(directory), "%s/directory.sqlite3.tmp", backup_dir); + put_file(complete, "complete"); put_file(other, "notes"); + TEST_ASSERT_EQUAL_INT(0, symlink(sentinel, link)); + TEST_ASSERT_EQUAL_INT(0, mkdir(directory, 0700)); + TEST_ASSERT_EQUAL_INT(0, cleanup_stale_backup_artifacts(backup_dir)); + TEST_ASSERT_EQUAL_INT(-1, access(artifact, F_OK)); + assert_bytes(complete, "complete"); assert_bytes(other, "notes"); + struct stat info; TEST_ASSERT_EQUAL_INT(0, lstat(link, &info)); TEST_ASSERT_TRUE(S_ISLNK(info.st_mode)); + TEST_ASSERT_EQUAL_INT(0, lstat(directory, &info)); TEST_ASSERT_TRUE(S_ISDIR(info.st_mode)); + assert_bytes(sentinel, "sentinel unchanged"); +} +static void test_parent_replacement_cannot_redirect_stale_cleanup(void) { + armed = true; + TEST_ASSERT_EQUAL_INT(0, cleanup_stale_backup_artifacts(backup_dir)); + TEST_ASSERT_TRUE(replaced); + assert_bytes(artifact, "replacement must survive"); + char old_artifact[320]; snprintf(old_artifact, sizeof(old_artifact), "%s/%s", moved_dir, artifact_name); + TEST_ASSERT_EQUAL_INT(-1, access(old_artifact, F_OK)); + assert_bytes(sentinel, "sentinel unchanged"); +} +static void test_leaf_symlink_replacement_does_not_remove_unvalidated_entry(void) { + armed = leaf_swap = true; + (void)cleanup_stale_backup_artifacts(backup_dir); + TEST_ASSERT_TRUE(replaced); + assert_bytes(sentinel, "sentinel unchanged"); + struct stat info; + TEST_ASSERT_EQUAL_INT(0, lstat(artifact, &info)); + TEST_ASSERT_TRUE(S_ISLNK(info.st_mode)); +} +static void test_missing_directory_is_noop(void) { + char missing[256]; snprintf(missing, sizeof(missing), "%s/missing", root); + TEST_ASSERT_EQUAL_INT(0, cleanup_stale_backup_artifacts(missing)); +} +static void test_leaf_regular_replacement_preserves_new_contents(void) { + armed = leaf_swap = regular_swap = true; + (void)cleanup_stale_backup_artifacts(backup_dir); + TEST_ASSERT_TRUE(replaced); + assert_bytes(artifact, "new regular file must survive"); +} +static void test_non_directory_returns_error(void) { + TEST_ASSERT_EQUAL_INT(-1, cleanup_stale_backup_artifacts(sentinel)); + assert_bytes(sentinel, "sentinel unchanged"); +} +static void test_readonly_open_backup_restore_preserve_database_contents(void) { + char source[256], copy[256], restored[256], associated_backups[300]; + snprintf(source, sizeof(source), "%s/database.sqlite", root); + snprintf(copy, sizeof(copy), "%s/copy.sqlite", root); + snprintf(restored, sizeof(restored), "%s/restored.sqlite", root); + snprintf(associated_backups, sizeof(associated_backups), "%s.backups", source); + TEST_ASSERT_EQUAL_INT(0, rename(backup_dir, associated_backups)); + safe_strcpy(backup_dir, associated_backups, sizeof(backup_dir), 0); + snprintf(artifact, sizeof(artifact), "%s/%s", backup_dir, artifact_name); + TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(source, &fixture_db)); + TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_exec(fixture_db, + "CREATE TABLE sample(value TEXT); INSERT INTO sample VALUES('preserved');", NULL, NULL, NULL)); + TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_close(fixture_db)); fixture_db = NULL; + memset(&g_config, 0, sizeof(g_config)); + fixture_config_loaded = true; + load_default_config(&g_config); + TEST_ASSERT_EQUAL_INT(0, init_database_ex(source, DB_INIT_READ_ONLY)); + TEST_ASSERT_EQUAL_INT(1, sqlite3_db_readonly(get_db_handle(), "main")); + assert_bytes(artifact, "stale partial backup"); + shutdown_database(); + TEST_ASSERT_EQUAL_INT(0, backup_database(source, copy, false)); + TEST_ASSERT_EQUAL_INT(0, restore_database_from_backup(copy, restored)); + TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open_v2(restored, &fixture_db, SQLITE_OPEN_READONLY, NULL)); + sqlite3_stmt *statement = NULL; + TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_prepare_v2(fixture_db, "SELECT value FROM sample", -1, &statement, NULL)); + int stepped = sqlite3_step(statement); + bool preserved = stepped == SQLITE_ROW && strcmp((const char *)sqlite3_column_text(statement, 0), "preserved") == 0; + sqlite3_finalize(statement); + TEST_ASSERT_TRUE(preserved); +} +static unsigned quarantine_count(char *retained, size_t length) { + DIR *dir = opendir(backup_dir); TEST_ASSERT_NOT_NULL(dir); + unsigned count = 0; struct dirent *entry; + while ((entry = readdir(dir))) if (strncmp(entry->d_name, ".cleanup-", 9) == 0) { + count++; + if (retained) snprintf(retained, length, "%s/%s/%s", backup_dir, entry->d_name, artifact_name); + } + closedir(dir); return count; +} +static void assert_clean_failure(void) { + TEST_ASSERT_EQUAL_INT(-1, cleanup_stale_backup_artifacts(backup_dir)); + assert_bytes(artifact, "stale partial backup"); + TEST_ASSERT_EQUAL_UINT(0, quarantine_count(NULL, 0)); +} +static void test_candidate_open_failure_preserves_artifact(void) { fail_candidate_open = true; assert_clean_failure(); } +static void test_private_directory_open_failure_preserves_artifact(void) { fail_private_open = true; assert_clean_failure(); } +static void test_private_directory_create_failure_preserves_artifact(void) { fail_mkdir = true; assert_clean_failure(); } +static void test_move_failure_preserves_artifact(void) { fail_move = true; assert_clean_failure(); } +static void test_unlink_failure_restores_without_overwrite(void) { + fail_delete = true; assert_clean_failure(); TEST_ASSERT_EQUAL_UINT(1, restore_calls); +} +static void test_private_directory_wrong_owner_is_rejected(void) { private_stat_fault = 1; assert_clean_failure(); } +static void test_private_directory_wrong_mode_is_rejected(void) { private_stat_fault = 2; assert_clean_failure(); } +static void test_private_directory_wrong_inode_is_rejected(void) { private_stat_fault = 3; assert_clean_failure(); } +static void retained_restore(int error, bool collision) { + armed = leaf_swap = regular_swap = true; + restore_error = error; restore_collision = collision; + TEST_ASSERT_EQUAL_INT(-1, cleanup_stale_backup_artifacts(backup_dir)); + TEST_ASSERT_TRUE(held_inode_verified); + TEST_ASSERT_EQUAL_UINT(1, restore_calls); + char retained[640]; TEST_ASSERT_EQUAL_UINT(1, quarantine_count(retained, sizeof(retained))); + assert_bytes(retained, "new regular file must survive"); + if (collision) assert_bytes(artifact, "concurrent destination must survive"); + else TEST_ASSERT_EQUAL_INT(-1, access(artifact, F_OK)); +} +static void test_restore_collision_preserves_both_objects(void) { retained_restore(0, true); } +static void test_unsupported_restore_retains_quarantine(void) { retained_restore(ENOSYS, false); } +static void test_restore_error_retains_quarantine(void) { retained_restore(EIO, false); } +static void test_changed_size_or_mtime_is_restored(void) { + change_after_move = true; + TEST_ASSERT_EQUAL_INT(-1, cleanup_stale_backup_artifacts(backup_dir)); + assert_bytes(artifact, "stale partial backup changed"); + TEST_ASSERT_EQUAL_UINT(1, restore_calls); TEST_ASSERT_EQUAL_UINT(0, quarantine_count(NULL, 0)); +} +static void test_crash_leftover_is_not_recursively_deleted(void) { + char private[320], retained[400]; + snprintf(private, sizeof(private), "%s/.cleanup-leftover", backup_dir); + snprintf(retained, sizeof(retained), "%s/%s", private, artifact_name); + TEST_ASSERT_EQUAL_INT(0, mkdir(private, 0700)); put_file(retained, "retained crash artifact"); + TEST_ASSERT_EQUAL_INT(0, cleanup_stale_backup_artifacts(backup_dir)); + assert_bytes(retained, "retained crash artifact"); TEST_ASSERT_EQUAL_UINT(1, quarantine_count(NULL, 0)); +} +static void test_fifo_artifact_is_skipped_without_blocking(void) { + char fifo[320]; snprintf(fifo, sizeof(fifo), "%s/fifo.sqlite3.tmp", backup_dir); + TEST_ASSERT_EQUAL_INT(0, mkfifo(fifo, 0600)); + TEST_ASSERT_EQUAL_INT(0, cleanup_stale_backup_artifacts(backup_dir)); + struct stat info; TEST_ASSERT_EQUAL_INT(0, lstat(fifo, &info)); TEST_ASSERT_TRUE(S_ISFIFO(info.st_mode)); +} +static void test_candidate_descriptor_pins_inode_until_replacement_is_preserved(void) { + armed = leaf_swap = regular_swap = true; + (void)cleanup_stale_backup_artifacts(backup_dir); + TEST_ASSERT_TRUE(held_inode_verified); + assert_bytes(artifact, "new regular file must survive"); +} +static void test_restored_changed_entry_is_not_revisited_in_same_pass(void) { + armed = leaf_swap = regular_swap = repeat_restored = true; + int result = cleanup_stale_backup_artifacts(backup_dir); + repeat_restored = false; + TEST_ASSERT_EQUAL_INT(-1, result); + assert_bytes(artifact, "new regular file must survive"); + TEST_ASSERT_EQUAL_UINT(1, restore_calls); +} +static void test_uuid_failure_preserves_artifact(void) { + fail_uuid = true; assert_clean_failure(); TEST_ASSERT_EQUAL_UINT(0, mkdir_calls); +} +static void test_directory_name_collisions_have_bounded_retries(void) { + collide_mkdir = true; assert_clean_failure(); TEST_ASSERT_EQUAL_UINT(4, mkdir_calls); +} +static void test_unsupported_filesystem_restore_retains_quarantine(void) { retained_restore(EINVAL, false); } +static void test_setgid_parent_still_allows_private_cleanup(void) { + TEST_ASSERT_EQUAL_INT(0, chmod(backup_dir, 02700)); + TEST_ASSERT_EQUAL_INT(0, cleanup_stale_backup_artifacts(backup_dir)); + TEST_ASSERT_EQUAL_INT(-1, access(artifact, F_OK)); + TEST_ASSERT_EQUAL_UINT(0, quarantine_count(NULL, 0)); +} +static void test_populated_private_directory_substitution_never_overwrites_data(void) { + char prepared[256], unrelated[320], retained[640]; + snprintf(prepared, sizeof(prepared), "%s/prepared-private", root); + snprintf(unrelated, sizeof(unrelated), "%s/%s", prepared, artifact_name); + TEST_ASSERT_EQUAL_INT(0, mkdir(prepared, 0700)); + put_file(unrelated, "unrelated private data"); + substitute_private_directory = true; + TEST_ASSERT_EQUAL_INT(-1, cleanup_stale_backup_artifacts(backup_dir)); + assert_bytes(artifact, "stale partial backup"); + TEST_ASSERT_EQUAL_UINT(1, quarantine_count(retained, sizeof(retained))); + assert_bytes(retained, "unrelated private data"); +} +static void test_unsupported_initial_move_preserves_original_and_cleans_empty_directory(void) { + move_error = ENOSYS; assert_clean_failure(); TEST_ASSERT_EQUAL_UINT(0, restore_calls); +} +int main(void) { + UNITY_BEGIN(); + RUN_TEST(test_normal_stale_cleanup_preserves_completed_unrelated_and_symlinks); + RUN_TEST(test_parent_replacement_cannot_redirect_stale_cleanup); + RUN_TEST(test_leaf_symlink_replacement_does_not_remove_unvalidated_entry); + RUN_TEST(test_leaf_regular_replacement_preserves_new_contents); + RUN_TEST(test_missing_directory_is_noop); + RUN_TEST(test_non_directory_returns_error); + RUN_TEST(test_readonly_open_backup_restore_preserve_database_contents); + RUN_TEST(test_candidate_open_failure_preserves_artifact); + RUN_TEST(test_private_directory_open_failure_preserves_artifact); + RUN_TEST(test_private_directory_create_failure_preserves_artifact); + RUN_TEST(test_move_failure_preserves_artifact); + RUN_TEST(test_unlink_failure_restores_without_overwrite); + RUN_TEST(test_private_directory_wrong_owner_is_rejected); + RUN_TEST(test_private_directory_wrong_mode_is_rejected); + RUN_TEST(test_private_directory_wrong_inode_is_rejected); + RUN_TEST(test_restore_collision_preserves_both_objects); + RUN_TEST(test_unsupported_restore_retains_quarantine); + RUN_TEST(test_restore_error_retains_quarantine); + RUN_TEST(test_changed_size_or_mtime_is_restored); + RUN_TEST(test_crash_leftover_is_not_recursively_deleted); + RUN_TEST(test_fifo_artifact_is_skipped_without_blocking); + RUN_TEST(test_candidate_descriptor_pins_inode_until_replacement_is_preserved); + RUN_TEST(test_restored_changed_entry_is_not_revisited_in_same_pass); + RUN_TEST(test_uuid_failure_preserves_artifact); + RUN_TEST(test_directory_name_collisions_have_bounded_retries); + RUN_TEST(test_unsupported_filesystem_restore_retains_quarantine); + RUN_TEST(test_setgid_parent_still_allows_private_cleanup); + RUN_TEST(test_populated_private_directory_substitution_never_overwrites_data); + RUN_TEST(test_unsupported_initial_move_preserves_original_and_cleans_empty_directory); + return UNITY_END(); +} diff --git a/tests/unit/test_t7_25_retention.c b/tests/unit/test_t7_25_retention.c new file mode 100644 index 000000000..ae47491f0 --- /dev/null +++ b/tests/unit/test_t7_25_retention.c @@ -0,0 +1,457 @@ +#define _GNU_SOURCE +/* Exercise the private retention helper, without changing production visibility + * or opening an application database. The production implementation is intact. */ +#include "../../src/database/db_core.c" + +#include "unity.h" + +static char root[128], backups[256], moved[256], old_path[384], newer_path[384]; +static char saved_old[256], target[256]; +static unsigned inspected_files, swaps; +static int swap_kind; +static bool fixture_error, active; +static struct stat old_identity, newer_identity; +static unsigned seen_mask, candidate_stats, candidate_opens, fault_hits, pins_at_swap; +static int owned_fds[256], owned_roles[256], owned_count, private_fd; +static DIR *held_directory; +static bool repeat_name, repeated_name, repeat_ready; +static struct dirent repeat_entry; +static int fail_open_after, fail_stat_after, move_error; +static bool fail_realloc, fail_strdup, fail_read, fail_delete, restore_collision, change_after_move; + +static unsigned role_of(int fd) { + for (int i = 0; i < owned_count; ++i) if (owned_fds[i] == fd) return (unsigned)owned_roles[i]; + return 0; +} +static unsigned pin_count(void) { + unsigned n = 0; + for (int i = 0; i < owned_count; ++i) if (owned_roles[i] == 1) ++n; + return n; +} + +static int write_bytes(const char *path, const char *value) { + FILE *file = fopen(path, "wb"); + if (!file) return -1; + size_t length = strlen(value); + int result = fwrite(value, 1, length, file) == length ? 0 : -1; + if (fclose(file) != 0) result = -1; + return result; +} + +static void replace_after_inspection(void) { + int kind = swap_kind; + swap_kind = 0; + swaps++; + pins_at_swap = pin_count(); + if (kind == 1) { + if (rename(backups, moved) != 0 || mkdir(backups, 0700) != 0 || + write_bytes(old_path, "replacement-parent-old") != 0 || + write_bytes(newer_path, "replacement-parent-newer") != 0) fixture_error = true; + } else { + if (rename(old_path, saved_old) != 0) { fixture_error = true; return; } + if (kind == 2) { + if (write_bytes(old_path, "replacement-leaf") != 0) fixture_error = true; + } else if (symlink(target, old_path) != 0) { + fixture_error = true; + } + } +} + +static void inspected(const struct stat *info) { + candidate_stats++; + if (!swap_kind) return; + unsigned bit = same_backup_inode(info, &old_identity) ? 1u : + same_backup_inode(info, &newer_identity) ? 2u : 0u; + if (bit && !(seen_mask & bit)) { + seen_mask |= bit; inspected_files++; + if (seen_mask == 3) replace_after_inspection(); + } +} +int __real_stat(const char *, struct stat *); +int __wrap_stat(const char *path, struct stat *info) { + int result = __real_stat(path, info); + if (result == 0 && active && (!strcmp(path, old_path) || !strcmp(path, newer_path))) inspected(info); + return result; +} +int __real_openat(int, const char *, int, ...); +int __wrap_openat(int fd, const char *name, int flags, ...) { + mode_t mode = 0; + if (flags & O_CREAT) { va_list a; va_start(a, flags); mode = va_arg(a, int); va_end(a); } + bool candidate = active && has_suffix(name, ".sqlite3") && !(flags & O_DIRECTORY); + if (candidate && fail_open_after >= 0 && (int)candidate_opens == fail_open_after) { + fault_hits++; errno = EMFILE; return -1; + } + int result = __real_openat(fd, name, flags, mode); + if (result >= 0 && active) { + if (owned_count >= 256) { fixture_error = true; close(result); errno = EMFILE; return -1; } + owned_fds[owned_count] = result; + owned_roles[owned_count++] = candidate ? 1 : 2; + if (candidate) candidate_opens++; + if (flags & O_DIRECTORY) private_fd = result; + } + return result; +} +int __real_close(int); +int __wrap_close(int fd) { + int result = __real_close(fd); + for (int i = 0; i < owned_count; ++i) if (owned_fds[i] == fd) { + owned_fds[i] = owned_fds[--owned_count]; owned_roles[i] = owned_roles[owned_count]; break; + } + if (fd == private_fd) private_fd = -1; + return result; +} +int __real_fstat(int, struct stat *); +int __wrap_fstat(int fd, struct stat *info) { + if (active && role_of(fd) == 1 && fail_stat_after >= 0 && (int)candidate_stats == fail_stat_after) { + fault_hits++; errno = EIO; return -1; + } + int result = __real_fstat(fd, info); + if (result == 0 && active && role_of(fd) == 1) inspected(info); + return result; +} +DIR *__real_opendir(const char *); +DIR *__wrap_opendir(const char *path) { + DIR *d = __real_opendir(path); + if (active && d && !strcmp(path, backups)) held_directory = d; + return d; +} +int __real_closedir(DIR *); +int __wrap_closedir(DIR *d) { + if (d == held_directory) held_directory = NULL; + return __real_closedir(d); +} +struct dirent *__real_readdir(DIR *); +struct dirent *__wrap_readdir(DIR *d) { + if (active && fail_read && candidate_stats) { fault_hits++; errno = EIO; return NULL; } + struct dirent *entry = __real_readdir(d); + if (active && repeat_name) { + if (entry && !strcmp(entry->d_name, "20240102T000000Z.sqlite3")) { + repeat_entry = *entry; repeat_ready = true; + } else if (!entry && repeat_ready && !repeated_name) { + repeated_name = true; return &repeat_entry; + } + } + return entry; +} +void *__real_realloc(void *, size_t); +void *__wrap_realloc(void *p, size_t n) { + if (active && fail_realloc) { fail_realloc = false; fault_hits++; errno = ENOMEM; return NULL; } + return __real_realloc(p, n); +} +char *__real_strdup(const char *); +char *__wrap_strdup(const char *p) { + if (active && fail_strdup) { fail_strdup = false; fault_hits++; errno = ENOMEM; return NULL; } + return __real_strdup(p); +} +long __real_syscall(long, ...); +long __wrap_syscall(long number, ...) { + if (number != SYS_renameat2) { fixture_error = true; errno = ENOSYS; return -1; } + va_list a; va_start(a, number); + int from = va_arg(a, int); const char *name = va_arg(a, const char *); + int to = va_arg(a, int); const char *dest = va_arg(a, const char *); + unsigned flags = va_arg(a, unsigned); va_end(a); + if (flags != 1U) { fixture_error = true; errno = EINVAL; return -1; } + if (active && to == private_fd && move_error) { fault_hits++; errno = move_error; return -1; } + long result = __real_syscall(number, from, name, to, dest, flags); + if (active && result == 0 && to == private_fd) { + if (restore_collision && write_bytes(old_path, "competing-path") != 0) fixture_error = true; + if (change_after_move) { + int file = __real_openat(to, dest, O_WRONLY | O_APPEND | O_CLOEXEC); + if (file < 0) fixture_error = true; + else { if (write(file, "-changed", 8) != 8) fixture_error = true; __real_close(file); } + } + } + return result; +} +int __real_unlinkat(int, const char *, int); +int __wrap_unlinkat(int fd, const char *name, int flags) { + if (active && fd == private_fd && flags == 0 && fail_delete) { + fault_hits++; errno = EIO; return -1; + } + return __real_unlinkat(fd, name, flags); +} +static int prune(int count) { + active = true; + int result = prune_timestamped_backups(backups, count); + active = false; + return result; +} + +static bool exists(const char *path) { + struct stat info; + return lstat(path, &info) == 0; +} + +static void assert_bytes(const char *path, const char *expected) { + FILE *file = fopen(path, "rb"); + TEST_ASSERT_NOT_NULL_MESSAGE(file, path); + char bytes[128] = {0}; + size_t count = fread(bytes, 1, sizeof(bytes), file); + int close_result = fclose(file); + TEST_ASSERT_EQUAL_INT(0, close_result); + TEST_ASSERT_EQUAL_size_t(strlen(expected), count); + TEST_ASSERT_EQUAL_MEMORY(expected, bytes, count); +} + +/* This tree is created by this process under mkdtemp; never follow symlinks. */ +static int remove_owned_tree(const char *path) { + DIR *dir = opendir(path); + if (!dir) return -1; + int result = 0; + struct dirent *entry; + while ((entry = readdir(dir))) { + if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue; + char child[768]; + int length = snprintf(child, sizeof(child), "%s/%s", path, entry->d_name); + if (length < 0 || (size_t)length >= sizeof(child)) { result = -1; continue; } + struct stat info; + if (lstat(child, &info) != 0) { result = -1; continue; } + if (S_ISDIR(info.st_mode)) { + if (remove_owned_tree(child) != 0) result = -1; + } else if (unlink(child) != 0) result = -1; + } + if (closedir(dir) != 0) result = -1; + if (rmdir(path) != 0) result = -1; + return result; +} + +void setUp(void) { + swap_kind = 0; + swaps = inspected_files = seen_mask = candidate_stats = candidate_opens = fault_hits = pins_at_swap = 0; + owned_count = 0; private_fd = -1; held_directory = NULL; active = false; + fail_open_after = fail_stat_after = -1; move_error = 0; + fail_realloc = fail_strdup = fail_read = fail_delete = restore_collision = change_after_move = false; + repeat_name = repeated_name = repeat_ready = false; + fixture_error = false; + strcpy(root, "/tmp/lightnvr-retention-identity-XXXXXX"); + TEST_ASSERT_NOT_NULL(mkdtemp(root)); + snprintf(backups, sizeof(backups), "%s/backups", root); + snprintf(moved, sizeof(moved), "%s/moved-backups", root); + snprintf(saved_old, sizeof(saved_old), "%s/inspected-old", root); + snprintf(target, sizeof(target), "%s/symlink-target", root); + snprintf(old_path, sizeof(old_path), "%s/20240101T000000Z.sqlite3", backups); + snprintf(newer_path, sizeof(newer_path), "%s/20240102T000000Z.sqlite3", backups); + TEST_ASSERT_EQUAL_INT(0, mkdir(backups, 0700)); + TEST_ASSERT_EQUAL_INT(0, write_bytes(old_path, "inspected-old")); + TEST_ASSERT_EQUAL_INT(0, write_bytes(newer_path, "inspected-newer")); + TEST_ASSERT_EQUAL_INT(0, write_bytes(target, "outside-target")); + TEST_ASSERT_EQUAL_INT(0, lstat(old_path, &old_identity)); + TEST_ASSERT_EQUAL_INT(0, lstat(newer_path, &newer_identity)); +} + +void tearDown(void) { + swap_kind = 0; active = false; + int leaked = owned_count; + while (owned_count) __real_close(owned_fds[--owned_count]); + bool dir_leaked = held_directory != NULL; + if (held_directory) { __real_closedir(held_directory); held_directory = NULL; } + int cleanup = remove_owned_tree(root); + TEST_ASSERT_FALSE(fixture_error); + TEST_ASSERT_EQUAL_INT(0, leaked); + TEST_ASSERT_FALSE(dir_leaked); + TEST_ASSERT_EQUAL_INT(0, cleanup); +} + +void test_retention_keeps_newest_and_excludes_unpublished_artifacts(void) { + char temporary[384], note[384]; + snprintf(temporary, sizeof(temporary), "%s/20230101T000000Z.sqlite3.partial", backups); + snprintf(note, sizeof(note), "%s/operator-note.txt", backups); + TEST_ASSERT_EQUAL_INT(0, write_bytes(temporary, "unfinished")); + TEST_ASSERT_EQUAL_INT(0, write_bytes(note, "note")); + TEST_ASSERT_EQUAL_INT(0, prune(1)); + TEST_ASSERT_FALSE(exists(old_path)); + assert_bytes(newer_path, "inspected-newer"); + assert_bytes(temporary, "unfinished"); + assert_bytes(note, "note"); +} + +void test_retention_within_limit_preserves_all_bytes(void) { + TEST_ASSERT_EQUAL_INT(0, prune(2)); + assert_bytes(old_path, "inspected-old"); + assert_bytes(newer_path, "inspected-newer"); +} + +void test_parent_replacement_cannot_redirect_retention_unlink(void) { + swap_kind = 1; + int result = prune(1); + TEST_ASSERT_FALSE(fixture_error); + TEST_ASSERT_EQUAL_UINT(1, swaps); + assert_bytes(old_path, "replacement-parent-old"); + assert_bytes(newer_path, "replacement-parent-newer"); + char inspected[384]; + snprintf(inspected, sizeof(inspected), "%s/20240101T000000Z.sqlite3", moved); + TEST_ASSERT_FALSE(exists(inspected)); + snprintf(inspected, sizeof(inspected), "%s/20240102T000000Z.sqlite3", moved); + assert_bytes(inspected, "inspected-newer"); + TEST_ASSERT_EQUAL_INT(0, result); +} + +void test_changed_regular_leaf_is_retained_after_inspection(void) { + swap_kind = 2; + int result = prune(1); + TEST_ASSERT_FALSE(fixture_error); + TEST_ASSERT_EQUAL_UINT(1, swaps); + assert_bytes(old_path, "replacement-leaf"); + assert_bytes(saved_old, "inspected-old"); + assert_bytes(newer_path, "inspected-newer"); + TEST_ASSERT_NOT_EQUAL(0, result); +} + +void test_changed_symlink_leaf_and_target_are_retained(void) { + swap_kind = 3; + int result = prune(1); + TEST_ASSERT_FALSE(fixture_error); + TEST_ASSERT_EQUAL_UINT(1, swaps); + struct stat info; + TEST_ASSERT_EQUAL_INT(0, lstat(old_path, &info)); + TEST_ASSERT_TRUE(S_ISLNK(info.st_mode)); + assert_bytes(target, "outside-target"); + assert_bytes(saved_old, "inspected-old"); + TEST_ASSERT_NOT_EQUAL(0, result); +} + +static void assert_both_originals(void) { + assert_bytes(old_path, "inspected-old"); + assert_bytes(newer_path, "inspected-newer"); +} +static void test_collection_emfile_preserves_all_and_closes_pins(void) { + fail_open_after = 1; + TEST_ASSERT_NOT_EQUAL(0, prune(1)); + TEST_ASSERT_EQUAL_UINT(1, fault_hits); + TEST_ASSERT_EQUAL_UINT(1, candidate_opens); + assert_both_originals(); +} +static void test_collection_stat_failure_preserves_all(void) { + fail_stat_after = 1; + TEST_ASSERT_NOT_EQUAL(0, prune(1)); + TEST_ASSERT_EQUAL_UINT(1, fault_hits); + assert_both_originals(); +} +static void test_collection_realloc_failure_preserves_all(void) { + fail_realloc = true; + TEST_ASSERT_NOT_EQUAL(0, prune(1)); + TEST_ASSERT_EQUAL_UINT(1, fault_hits); + assert_both_originals(); +} +static void test_collection_strdup_failure_preserves_all(void) { + fail_strdup = true; + TEST_ASSERT_NOT_EQUAL(0, prune(1)); + TEST_ASSERT_EQUAL_UINT(1, fault_hits); + assert_both_originals(); +} +static void test_readdir_error_does_not_turn_partial_collection_into_retention(void) { + fail_read = true; + TEST_ASSERT_NOT_EQUAL(0, prune(0)); + TEST_ASSERT_EQUAL_UINT(1, fault_hits); + assert_both_originals(); +} +static void test_unsupported_move_preserves_published_backup(void) { + move_error = ENOSYS; + TEST_ASSERT_NOT_EQUAL(0, prune(1)); + TEST_ASSERT_EQUAL_UINT(1, fault_hits); + assert_both_originals(); +} +static void test_unlink_failure_restores_without_overwrite(void) { + fail_delete = true; + TEST_ASSERT_NOT_EQUAL(0, prune(1)); + TEST_ASSERT_EQUAL_UINT(1, fault_hits); + assert_both_originals(); +} +static void test_restore_collision_retains_quarantined_original(void) { + fail_delete = true; restore_collision = true; + TEST_ASSERT_NOT_EQUAL(0, prune(1)); + TEST_ASSERT_EQUAL_UINT(1, fault_hits); + assert_bytes(old_path, "competing-path"); + assert_bytes(newer_path, "inspected-newer"); + DIR *d = opendir(backups); TEST_ASSERT_NOT_NULL(d); + char retained[768] = {0}; unsigned found = 0; struct dirent *e; + while ((e = readdir(d))) if (!strncmp(e->d_name, ".cleanup-", 9)) { + snprintf(retained, sizeof(retained), "%s/%s/20240101T000000Z.sqlite3", backups, e->d_name); found++; + } + TEST_ASSERT_EQUAL_INT(0, closedir(d)); + TEST_ASSERT_EQUAL_UINT(1, found); + assert_bytes(retained, "inspected-old"); +} +static void test_changed_after_move_is_restored_and_pass_stops(void) { + change_after_move = true; + TEST_ASSERT_NOT_EQUAL(0, prune(0)); + assert_bytes(old_path, "inspected-old-changed"); + assert_bytes(newer_path, "inspected-newer"); +} +static void test_both_selection_inodes_remain_pinned_during_replacement(void) { + swap_kind = 2; + TEST_ASSERT_NOT_EQUAL(0, prune(1)); + TEST_ASSERT_EQUAL_UINT(1, swaps); + TEST_ASSERT_EQUAL_UINT(2, pins_at_swap); + assert_bytes(old_path, "replacement-leaf"); + assert_bytes(saved_old, "inspected-old"); +} +static void test_more_than_sixty_four_backups_preserve_configured_order(void) { + for (unsigned i = 0; i < 70; ++i) { + char path[384]; snprintf(path, sizeof(path), "%s/20230101T%06uZ.sqlite3", backups, i); + TEST_ASSERT_EQUAL_INT(0, write_bytes(path, "older")); + } + TEST_ASSERT_EQUAL_INT(0, prune(2)); + assert_both_originals(); + for (unsigned i = 0; i < 70; ++i) { + char path[384]; snprintf(path, sizeof(path), "%s/20230101T%06uZ.sqlite3", backups, i); + TEST_ASSERT_FALSE(exists(path)); + } +} +static void test_published_suffix_symlink_and_fifo_are_not_counted_or_removed(void) { + char link[384], fifo[384]; + snprintf(link, sizeof(link), "%s/19000101T000000Z.sqlite3", backups); + snprintf(fifo, sizeof(fifo), "%s/19000102T000000Z.sqlite3", backups); + TEST_ASSERT_EQUAL_INT(0, symlink(target, link)); + TEST_ASSERT_EQUAL_INT(0, mkfifo(fifo, 0600)); + TEST_ASSERT_EQUAL_INT(0, prune(2)); + assert_both_originals(); + struct stat st; + TEST_ASSERT_EQUAL_INT(0, lstat(link, &st)); TEST_ASSERT_TRUE(S_ISLNK(st.st_mode)); + TEST_ASSERT_EQUAL_INT(0, lstat(fifo, &st)); TEST_ASSERT_TRUE(S_ISFIFO(st.st_mode)); + assert_bytes(target, "outside-target"); +} + +static void test_repeated_selected_name_aborts_before_any_deletion(void) { + repeat_name = true; + int result = prune(1); + TEST_ASSERT_TRUE(repeated_name); + assert_both_originals(); + TEST_ASSERT_NOT_EQUAL(0, result); +} +static void test_distinct_hardlinked_names_keep_filename_count_semantics(void) { + char additional[384]; + snprintf(additional, sizeof(additional), "%s/20240103T000000Z.sqlite3", backups); + TEST_ASSERT_EQUAL_INT(0, link(newer_path, additional)); + TEST_ASSERT_EQUAL_INT(0, prune(1)); + TEST_ASSERT_FALSE(exists(old_path)); + TEST_ASSERT_FALSE(exists(newer_path)); + assert_bytes(additional, "inspected-newer"); +} + +int main(void) { + init_logger(); + UNITY_BEGIN(); + RUN_TEST(test_retention_keeps_newest_and_excludes_unpublished_artifacts); + RUN_TEST(test_retention_within_limit_preserves_all_bytes); + RUN_TEST(test_parent_replacement_cannot_redirect_retention_unlink); + RUN_TEST(test_changed_regular_leaf_is_retained_after_inspection); + RUN_TEST(test_changed_symlink_leaf_and_target_are_retained); + RUN_TEST(test_collection_emfile_preserves_all_and_closes_pins); + RUN_TEST(test_collection_stat_failure_preserves_all); + RUN_TEST(test_collection_realloc_failure_preserves_all); + RUN_TEST(test_collection_strdup_failure_preserves_all); + RUN_TEST(test_readdir_error_does_not_turn_partial_collection_into_retention); + RUN_TEST(test_unsupported_move_preserves_published_backup); + RUN_TEST(test_unlink_failure_restores_without_overwrite); + RUN_TEST(test_restore_collision_retains_quarantined_original); + RUN_TEST(test_changed_after_move_is_restored_and_pass_stops); + RUN_TEST(test_both_selection_inodes_remain_pinned_during_replacement); + RUN_TEST(test_more_than_sixty_four_backups_preserve_configured_order); + RUN_TEST(test_published_suffix_symlink_and_fifo_are_not_counted_or_removed); + RUN_TEST(test_repeated_selected_name_aborts_before_any_deletion); + RUN_TEST(test_distinct_hardlinked_names_keep_filename_count_semantics); + int result = UNITY_END(); + shutdown_logger(); + return result; +} diff --git a/tests/unit/test_t7_26.c b/tests/unit/test_t7_26.c new file mode 100644 index 000000000..5c85663f9 --- /dev/null +++ b/tests/unit/test_t7_26.c @@ -0,0 +1,501 @@ +#define _GNU_SOURCE +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "unity.h" +#include "core/config.h" +#include "core/path_utils.h" +#include "database/db_core.h" +#include "database/db_recordings.h" +#include "database/db_storage_migrations.h" +#include "database/db_storage_targets.h" +#include "storage/storage_migration.h" +#include "utils/strings.h" + +static char root[128], source_root[256], destination_root[256], db_path[256]; +static char source_path[MAX_PATH_LENGTH], destination_path[MAX_PATH_LENGTH]; +static char temporary_path[MAX_PATH_LENGTH], sentinel[256], verified_part[256]; +static char source_uuid[LIGHTNVR_UUID_STRING_SIZE], destination_uuid[LIGHTNVR_UUID_STRING_SIZE]; +static bool armed, injected; +static int replacement_kind, publication_error, destination_stat_error; +static bool fail_part_unlink; +static int quarantine_error, committing_replacement; +static bool restore_collision, commit_failure_replacement, cancel_after_publication; +static char private_artifact[MAX_PATH_LENGTH], active_job[LIGHTNVR_UUID_STRING_SIZE]; +static char relocated_parent[256]; +int __real_unlink(const char *); +int __wrap_unlink(const char *path) { + if (fail_part_unlink && !strcmp(path, temporary_path)) { errno = EACCES; return -1; } + return __real_unlink(path); +} +static const char payload[] = "verified source recording"; +static void put_file(const char *path, const char *bytes) { + TEST_ASSERT_EQUAL_INT(0, ensure_path(path)); + FILE *file = fopen(path, "w"); TEST_ASSERT_NOT_NULL(file); + size_t length = fwrite(bytes, 1, strlen(bytes), file); int closed = fclose(file); + TEST_ASSERT_EQUAL_UINT(strlen(bytes), length); TEST_ASSERT_EQUAL_INT(0, closed); +} +static void inject_before_publication(void) { + armed = false; injected = true; + if (replacement_kind == 1) put_file(destination_path, "concurrent destination data"); + else if (replacement_kind == 2) TEST_ASSERT_EQUAL_INT(0, symlink(sentinel, destination_path)); + else if (replacement_kind == 4) put_file(temporary_path, "modified verified inode"); + else { + TEST_ASSERT_EQUAL_INT(0, rename(temporary_path, verified_part)); + put_file(temporary_path, "unverified replacement data"); + } +} +int __real_lstat(const char *, struct stat *); +int __wrap_lstat(const char *path, struct stat *info) { + if (destination_stat_error && !strcmp(path, destination_path)) { errno = destination_stat_error; return -1; } + int result = __real_lstat(path, info); int saved = errno; + if (armed && result != 0 && saved == ENOENT && strcmp(path, destination_path) == 0 && + access(temporary_path, F_OK) == 0) inject_before_publication(); + errno = saved; return result; +} +long __real_syscall(long, ...); +long __wrap_syscall(long number, ...) { + if (number != SYS_renameat2) { + TEST_FAIL_MESSAGE("Unexpected syscall in publication seam"); errno = ENOSYS; return -1; + } + va_list args; va_start(args, number); + int oldfd = va_arg(args, int); const char *oldname = va_arg(args, const char *); + int newfd = va_arg(args, int); const char *newname = va_arg(args, const char *); + unsigned int flags = va_arg(args, unsigned int); va_end(args); + bool quarantine = !strcmp(oldname, temporary_path) || + (!strcmp(newname, "verified") && strstr(oldname, ".migration-")); + if (armed && ((replacement_kind >= 3 && quarantine) || + (replacement_kind < 3 && (!strcmp(newname, destination_path) || !strcmp(newname, "clip.mp4"))))) + inject_before_publication(); + if (quarantine_error && quarantine) { errno = quarantine_error; return -1; } + if (publication_error && (!strcmp(newname, destination_path) || !strcmp(newname, "clip.mp4"))) { + errno = publication_error; return -1; + } + if (quarantine && !strcmp(newname, "verified")) { + char descriptor_path[64], directory[MAX_PATH_LENGTH]; + snprintf(descriptor_path, sizeof(descriptor_path), "/proc/self/fd/%d", newfd); + ssize_t length = readlink(descriptor_path, directory, sizeof(directory)-1); + TEST_ASSERT_TRUE(length > 0); directory[length] = '\0'; + int size = snprintf(private_artifact, sizeof(private_artifact), "%s/verified", directory); + TEST_ASSERT_TRUE(size > 0 && (size_t)size < sizeof(private_artifact)); + } + if (restore_collision && !strcmp(oldname, "verified") && strstr(newname, ".part")) + put_file(temporary_path, "restore collision sentinel"); + long result = __real_syscall(number, oldfd, oldname, newfd, newname, flags); + if (result == 0 && cancel_after_publication && !strcmp(newname, "clip.mp4")) { + cancel_after_publication = false; + TEST_ASSERT_EQUAL_INT(0, rename(destination_path, verified_part)); + put_file(destination_path, "cancelled substitution sentinel"); + storage_migration_job_t job; + TEST_ASSERT_EQUAL_INT(DB_STORAGE_MIGRATION_OK, db_storage_migration_request_cancel(active_job, &job)); + } + return result; +} +db_storage_migration_result_t __real_db_storage_migration_update_progress( + const char *, const char *, uint64_t, uint64_t); +db_storage_migration_result_t __wrap_db_storage_migration_update_progress( + const char *uuid, const char *state, uint64_t copied, uint64_t total) { + if (committing_replacement && !strcmp(state, "committing")) { + int kind = committing_replacement; committing_replacement = 0; + if (kind == 2) { + char parent[MAX_PATH_LENGTH], relocated_file[MAX_PATH_LENGTH]; + strcpy(parent, destination_path); *strrchr(parent, '/') = 0; + TEST_ASSERT_EQUAL_INT(0, rename(parent, relocated_parent)); + TEST_ASSERT_EQUAL_INT(0, mkdir(parent, 0700)); + int size = snprintf(relocated_file, sizeof(relocated_file), "%s/clip.mp4", relocated_parent); + TEST_ASSERT_TRUE(size > 0 && (size_t)size < sizeof(relocated_file)); + TEST_ASSERT_EQUAL_INT(0, link(relocated_file, destination_path)); + } else { + TEST_ASSERT_EQUAL_INT(0, rename(destination_path, verified_part)); + put_file(destination_path, "substituted published destination"); + } + } + return __real_db_storage_migration_update_progress(uuid, state, copied, total); +} +db_storage_migration_result_t __real_db_storage_migration_commit_location( + const storage_migration_job_t *, const char *, const char *); +db_storage_migration_result_t __wrap_db_storage_migration_commit_location( + const storage_migration_job_t *job, const char *path, const char *checksum) { + if (commit_failure_replacement) { + TEST_ASSERT_EQUAL_INT(0, rename(destination_path, verified_part)); + put_file(destination_path, "failed commit substitution sentinel"); + return DB_STORAGE_MIGRATION_ERROR; + } + return __real_db_storage_migration_commit_location(job, path, checksum); +} +static void assert_bytes(const char *path, const char *expected) { + FILE *file = fopen(path, "r"); TEST_ASSERT_NOT_NULL(file); + char actual[128] = {0}; size_t bytes = fread(actual, 1, sizeof(actual)-1, file); int closed = fclose(file); + TEST_ASSERT_EQUAL_INT(0, closed); TEST_ASSERT_EQUAL_UINT(strlen(expected), bytes); + TEST_ASSERT_EQUAL_STRING(expected, actual); +} +static void remove_owned_tree(const char *directory) { + DIR *dir = opendir(directory); if (!dir) return; + struct dirent *entry; + while ((entry = readdir(dir))) { + if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue; + char path[1024]; snprintf(path, sizeof(path), "%s/%s", directory, entry->d_name); + struct stat info; + if (__real_lstat(path, &info) == 0 && S_ISDIR(info.st_mode)) remove_owned_tree(path); + else unlink(path); + } + closedir(dir); rmdir(directory); +} +static void configure_targets(void) { + TEST_ASSERT_EQUAL_INT(0, db_storage_target_bootstrap_default(source_root, source_uuid)); + storage_target_t source; + TEST_ASSERT_EQUAL_INT(DB_STORAGE_TARGET_OK, db_storage_target_probe(source_uuid, true, &source)); + storage_target_t destination = {0}; + strcpy(destination.name, "T7.26 destination"); strcpy(destination.target_type, "filesystem"); + safe_strcpy(destination.root_path, destination_root, sizeof(destination.root_path), 0); + strcpy(destination.storage_class, "warm"); destination.enabled = true; + destination.high_watermark_pct = 90.0; destination.low_watermark_pct = 80.0; + TEST_ASSERT_EQUAL_INT(DB_STORAGE_TARGET_OK, db_storage_target_create(&destination)); + strcpy(destination_uuid, destination.uuid); +} +void setUp(void) { + armed = injected = fail_part_unlink = false; replacement_kind = publication_error = destination_stat_error = 0; quarantine_error = committing_replacement = 0; temporary_path[0] = '\0'; + TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_exec(get_db_handle(), + "DELETE FROM storage_migration_jobs;DELETE FROM detections;DELETE FROM recordings;DELETE FROM storage_targets;", + NULL, NULL, NULL)); + remove_owned_tree(source_root); remove_owned_tree(destination_root); remove_owned_tree(relocated_parent); + TEST_ASSERT_EQUAL_INT(0, mkdir(source_root, 0700)); TEST_ASSERT_EQUAL_INT(0, mkdir(destination_root, 0700)); + restore_collision = commit_failure_replacement = cancel_after_publication = false; private_artifact[0] = 0; + unlink(sentinel); unlink(verified_part); put_file(sentinel, "sentinel unchanged"); + configure_targets(); +} +void tearDown(void) { armed = fail_part_unlink = false; } +static uint64_t create_job(storage_migration_job_t *job, const char *operation) { + put_file(source_path, payload); + recording_metadata_t recording = {0}; + strcpy(recording.stream_name, "migration-camera"); + strcpy(recording.camera_uuid, "11111111-1111-4111-8111-111111111111"); + safe_strcpy(recording.file_path, source_path, sizeof(recording.file_path), 0); + strcpy(recording.storage_target_uuid, source_uuid); strcpy(recording.object_key, "camera/clip.mp4"); + strcpy(recording.placement_reason, "test"); strcpy(recording.codec, "h264"); + strcpy(recording.trigger_type, "continuous"); recording.start_time = 100; recording.end_time = 200; + recording.size_bytes = strlen(payload); recording.width = 1920; recording.height = 1080; + recording.fps = 30; recording.is_complete = true; recording.retention_override_days = -1; + recording.retention_tier = RETENTION_TIER_STANDARD; recording.disk_pressure_eligible = true; + uint64_t id = add_recording_metadata(&recording); TEST_ASSERT_NOT_EQUAL_UINT64(0, id); + TEST_ASSERT_EQUAL_INT(DB_STORAGE_MIGRATION_OK, + db_storage_migration_create_operation(id, destination_uuid, operation, 0, job)); + strcpy(active_job, job->uuid); + size_t needed = strlen(destination_path) + strlen(job->uuid) + sizeof(".migration-.part"); + TEST_ASSERT_TRUE(needed <= sizeof(temporary_path)); + strcpy(temporary_path, destination_path); + strcat(temporary_path, ".migration-"); strcat(temporary_path, job->uuid); strcat(temporary_path, ".part"); + return id; +} +static void assert_source_location(uint64_t id) { + recording_metadata_t recording; + TEST_ASSERT_EQUAL_INT(0, get_recording_metadata_by_id(id, &recording)); + TEST_ASSERT_EQUAL_STRING(source_uuid, recording.storage_target_uuid); + TEST_ASSERT_EQUAL_STRING(source_path, recording.file_path); + assert_bytes(source_path, payload); +} +static void test_normal_move_commits_verified_destination_and_removes_source(void) { + storage_migration_job_t job; uint64_t id = create_job(&job, "move"); + TEST_ASSERT_EQUAL_INT(1, storage_migration_process_one()); + TEST_ASSERT_EQUAL_INT(DB_STORAGE_MIGRATION_OK, db_storage_migration_get(job.uuid, &job)); + TEST_ASSERT_EQUAL_STRING("completed", job.state); TEST_ASSERT_EQUAL_UINT(64, strlen(job.checksum)); + assert_bytes(destination_path, payload); TEST_ASSERT_EQUAL_INT(-1, access(source_path, F_OK)); + TEST_ASSERT_EQUAL_INT(-1, access(temporary_path, F_OK)); + recording_metadata_t recording; + TEST_ASSERT_EQUAL_INT(0, get_recording_metadata_by_id(id, &recording)); + TEST_ASSERT_EQUAL_STRING(destination_uuid, recording.storage_target_uuid); + TEST_ASSERT_EQUAL_STRING(destination_path, recording.file_path); +} +static void test_normal_copy_keeps_source_and_verified_replica(void) { + storage_migration_job_t job; uint64_t id = create_job(&job, "copy"); + TEST_ASSERT_EQUAL_INT(1, storage_migration_process_one()); + TEST_ASSERT_EQUAL_INT(DB_STORAGE_MIGRATION_OK, db_storage_migration_get(job.uuid, &job)); + TEST_ASSERT_EQUAL_STRING("completed", job.state); assert_source_location(id); + assert_bytes(destination_path, payload); +} +static void test_regular_destination_appearing_after_check_is_not_overwritten(void) { + storage_migration_job_t job; uint64_t id = create_job(&job, "move"); + replacement_kind = 1; armed = true; + TEST_ASSERT_EQUAL_INT(1, storage_migration_process_one()); TEST_ASSERT_TRUE(injected); + assert_bytes(destination_path, "concurrent destination data"); assert_source_location(id); + TEST_ASSERT_EQUAL_INT(DB_STORAGE_MIGRATION_OK, db_storage_migration_get(job.uuid, &job)); + TEST_ASSERT_EQUAL_STRING("failed", job.state); +} +static void test_symlink_destination_appearing_after_check_is_retained(void) { + storage_migration_job_t job; uint64_t id = create_job(&job, "move"); + replacement_kind = 2; armed = true; + TEST_ASSERT_EQUAL_INT(1, storage_migration_process_one()); TEST_ASSERT_TRUE(injected); + struct stat info; TEST_ASSERT_EQUAL_INT(0, lstat(destination_path, &info)); + TEST_ASSERT_TRUE(S_ISLNK(info.st_mode)); assert_bytes(sentinel, "sentinel unchanged"); + assert_source_location(id); +} +static void test_existing_collision_survives_later_abandoned_cleanup(void) { + storage_migration_job_t job; uint64_t id = create_job(&job, "move"); + put_file(destination_path, "preexisting unrelated data"); + TEST_ASSERT_EQUAL_INT(1, storage_migration_process_one()); + assert_bytes(destination_path, "preexisting unrelated data"); assert_source_location(id); + TEST_ASSERT_EQUAL_INT(DB_STORAGE_MIGRATION_OK, db_storage_migration_get(job.uuid, &job)); + TEST_ASSERT_EQUAL_STRING("failed", job.state); + TEST_ASSERT_EQUAL_INT(0, storage_migration_process_one()); + assert_bytes(destination_path, "preexisting unrelated data"); assert_source_location(id); +} +static void test_unverified_temporary_replacement_is_not_committed(void) { + storage_migration_job_t job; uint64_t id = create_job(&job, "move"); + replacement_kind = 3; armed = true; + TEST_ASSERT_EQUAL_INT(1, storage_migration_process_one()); TEST_ASSERT_TRUE(injected); + assert_source_location(id); + TEST_ASSERT_EQUAL_INT(-1, access(destination_path, F_OK)); + assert_bytes(verified_part, payload); +} +static void test_unavailable_registered_source_preserves_metadata_and_bytes(void) { + storage_migration_job_t job; uint64_t id = create_job(&job, "move"); + char moved[256]; snprintf(moved, sizeof(moved), "%s/source-unavailable", root); + TEST_ASSERT_EQUAL_INT(0, rename(source_root, moved)); + int result = storage_migration_process_one(); + TEST_ASSERT_EQUAL_INT(0, rename(moved, source_root)); + TEST_ASSERT_EQUAL_INT(1, result); assert_source_location(id); + TEST_ASSERT_EQUAL_INT(-1, access(destination_path, F_OK)); +} +static void publication_failure(int error) { + storage_migration_job_t job; uint64_t id = create_job(&job, "move"); + publication_error = error; + TEST_ASSERT_EQUAL_INT(1, storage_migration_process_one()); + assert_source_location(id); TEST_ASSERT_EQUAL_INT(-1, access(destination_path, F_OK)); + TEST_ASSERT_EQUAL_INT(DB_STORAGE_MIGRATION_OK, db_storage_migration_get(job.uuid, &job)); + TEST_ASSERT_EQUAL_STRING("retry_wait", job.state); + assert_bytes(temporary_path, payload); +} +static void test_unsupported_atomic_publication_retains_source_and_temporary(void) { publication_failure(ENOSYS); } +static void test_unsupported_filesystem_publication_retains_source_and_temporary(void) { publication_failure(EINVAL); } +static void test_publication_io_failure_retains_source_and_temporary(void) { publication_failure(EIO); } +static void test_collision_cleanup_failure_never_overwrites_competing_destination(void) { + storage_migration_job_t job; uint64_t id = create_job(&job, "move"); + replacement_kind = 1; armed = fail_part_unlink = true; + TEST_ASSERT_EQUAL_INT(1, storage_migration_process_one()); TEST_ASSERT_TRUE(injected); + assert_bytes(destination_path, "concurrent destination data"); assert_source_location(id); + assert_bytes(temporary_path, payload); + TEST_ASSERT_EQUAL_INT(DB_STORAGE_MIGRATION_OK, db_storage_migration_get(job.uuid, &job)); + TEST_ASSERT_EQUAL_STRING("failed", job.state); +} +static int artifacts_cleaned(const char *uuid) { + sqlite3_stmt *statement = NULL; + TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_prepare_v2(get_db_handle(), + "SELECT artifacts_cleaned FROM storage_migration_jobs WHERE uuid=?", -1, &statement, NULL)); + sqlite3_bind_text(statement, 1, uuid, -1, SQLITE_TRANSIENT); + int result = sqlite3_step(statement); int cleaned = result == SQLITE_ROW ? sqlite3_column_int(statement, 0) : -1; + sqlite3_finalize(statement); TEST_ASSERT_EQUAL_INT(SQLITE_ROW, result); return cleaned; +} +static void assert_cleanup_pending(storage_migration_job_t *job, const char *state) { + TEST_ASSERT_EQUAL_INT(DB_STORAGE_MIGRATION_OK, db_storage_migration_get(job->uuid, job)); + TEST_ASSERT_EQUAL_STRING(state, job->state); TEST_ASSERT_EQUAL_INT(0, artifacts_cleaned(job->uuid)); + TEST_ASSERT_GREATER_THAN_INT64(0, job->next_attempt_at); + TEST_ASSERT_TRUE(job->last_error[0] != '\0'); +} +static void test_abandoned_cleanup_retains_unowned_symlink(void) { + storage_migration_job_t job; uint64_t id = create_job(&job, "move"); + TEST_ASSERT_EQUAL_INT(0, ensure_path(destination_path)); + TEST_ASSERT_EQUAL_INT(0, symlink(sentinel, destination_path)); + TEST_ASSERT_EQUAL_INT(1, storage_migration_process_one()); + TEST_ASSERT_EQUAL_INT(0, storage_migration_process_one()); + struct stat info; TEST_ASSERT_EQUAL_INT(0, lstat(destination_path, &info)); + TEST_ASSERT_TRUE(S_ISLNK(info.st_mode)); assert_bytes(sentinel, "sentinel unchanged"); + assert_source_location(id); assert_cleanup_pending(&job, "failed"); +} +static void test_cancelled_job_retains_unowned_final_destination(void) { + storage_migration_job_t job; uint64_t id = create_job(&job, "move"); + put_file(destination_path, "unowned cancelled destination"); + TEST_ASSERT_EQUAL_INT(DB_STORAGE_MIGRATION_OK, db_storage_migration_request_cancel(job.uuid, &job)); + TEST_ASSERT_EQUAL_INT(0, storage_migration_process_one()); + assert_bytes(destination_path, "unowned cancelled destination"); assert_source_location(id); + assert_cleanup_pending(&job, "cancelled"); +} +static void test_cancelled_job_without_artifacts_completes_cleanup(void) { + storage_migration_job_t job; uint64_t id = create_job(&job, "move"); + TEST_ASSERT_EQUAL_INT(DB_STORAGE_MIGRATION_OK, db_storage_migration_request_cancel(job.uuid, &job)); + TEST_ASSERT_EQUAL_INT(0, storage_migration_process_one()); + TEST_ASSERT_EQUAL_INT(DB_STORAGE_MIGRATION_OK, db_storage_migration_get(job.uuid, &job)); + TEST_ASSERT_EQUAL_STRING("cancelled", job.state); TEST_ASSERT_EQUAL_INT(1, artifacts_cleaned(job.uuid)); + assert_source_location(id); +} +static void test_matching_bytes_do_not_prove_abandoned_destination_ownership(void) { + storage_migration_job_t job; uint64_t id = create_job(&job, "move"); + put_file(destination_path, payload); + TEST_ASSERT_EQUAL_INT(DB_STORAGE_MIGRATION_OK, db_storage_migration_request_cancel(job.uuid, &job)); + TEST_ASSERT_EQUAL_INT(0, storage_migration_process_one()); + assert_bytes(destination_path, payload); assert_source_location(id); assert_cleanup_pending(&job, "cancelled"); +} +static void test_destination_inspection_error_keeps_cleanup_pending(void) { + storage_migration_job_t job; uint64_t id = create_job(&job, "move"); + TEST_ASSERT_EQUAL_INT(DB_STORAGE_MIGRATION_OK, db_storage_migration_request_cancel(job.uuid, &job)); + destination_stat_error = EACCES; + TEST_ASSERT_EQUAL_INT(0, storage_migration_process_one()); + destination_stat_error = 0; + assert_source_location(id); assert_cleanup_pending(&job, "cancelled"); +} +static void test_preexisting_temporary_is_preserved_while_fresh_attempt_succeeds(void) { + storage_migration_job_t job; uint64_t id = create_job(&job, "move"); + put_file(temporary_path, "preexisting temporary sentinel"); + TEST_ASSERT_EQUAL_INT(1, storage_migration_process_one()); + assert_bytes(temporary_path, "preexisting temporary sentinel"); + recording_metadata_t recording; + TEST_ASSERT_EQUAL_INT(0, get_recording_metadata_by_id(id, &recording)); + TEST_ASSERT_EQUAL_STRING(destination_uuid, recording.storage_target_uuid); + assert_bytes(destination_path, payload); + TEST_ASSERT_EQUAL_INT(-1, access(source_path, F_OK)); +} +static void test_preexisting_temporary_survives_failed_job_and_later_cleanup(void) { + storage_migration_job_t job; uint64_t id = create_job(&job, "move"); + put_file(temporary_path, "unowned temporary cleanup sentinel"); + put_file(destination_path, "unowned final cleanup sentinel"); + TEST_ASSERT_EQUAL_INT(1, storage_migration_process_one()); + assert_bytes(temporary_path, "unowned temporary cleanup sentinel"); + assert_source_location(id); + TEST_ASSERT_EQUAL_INT(0, storage_migration_process_one()); + assert_bytes(temporary_path, "unowned temporary cleanup sentinel"); + assert_bytes(destination_path, "unowned final cleanup sentinel"); + assert_source_location(id); assert_cleanup_pending(&job, "failed"); +} +static void test_retry_preserves_previous_attempt_and_uses_fresh_artifact(void) { + storage_migration_job_t job; uint64_t id = create_job(&job, "move"); + publication_error = EIO; + TEST_ASSERT_EQUAL_INT(1, storage_migration_process_one()); + assert_source_location(id); assert_bytes(temporary_path, payload); + publication_error = 0; + TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_exec(get_db_handle(), + "UPDATE storage_migration_jobs SET next_attempt_at=0;", NULL, NULL, NULL)); + TEST_ASSERT_EQUAL_INT(1, storage_migration_process_one()); + assert_bytes(temporary_path, payload); assert_bytes(destination_path, payload); + TEST_ASSERT_EQUAL_INT(DB_STORAGE_MIGRATION_OK, db_storage_migration_get(job.uuid, &job)); + TEST_ASSERT_EQUAL_STRING("completed", job.state); +} +static void test_preexisting_temporary_symlink_and_target_survive(void) { + storage_migration_job_t job; create_job(&job, "copy"); + TEST_ASSERT_EQUAL_INT(0, ensure_path(temporary_path)); + TEST_ASSERT_EQUAL_INT(0, symlink(sentinel, temporary_path)); + TEST_ASSERT_EQUAL_INT(1, storage_migration_process_one()); + assert_bytes(sentinel, "sentinel unchanged"); assert_bytes(destination_path, payload); + struct stat info; TEST_ASSERT_EQUAL_INT(0, lstat(temporary_path, &info)); + TEST_ASSERT_TRUE(S_ISLNK(info.st_mode)); +} +static void test_fresh_attempt_without_legacy_name_keeps_cleanup_pending(void) { + storage_migration_job_t job; uint64_t id = create_job(&job, "move"); + char fresh[MAX_PATH_LENGTH]; + int length = snprintf(fresh, sizeof(fresh), "%s.migration-%s.%s.part", destination_path, + job.uuid, "11111111-1111-4111-8111-111111111111"); + TEST_ASSERT_TRUE(length > 0 && (size_t)length < sizeof(fresh)); + put_file(fresh, "unowned fresh attempt"); + TEST_ASSERT_EQUAL_INT(DB_STORAGE_MIGRATION_OK, db_storage_migration_request_cancel(job.uuid, &job)); + TEST_ASSERT_EQUAL_INT(0, storage_migration_process_one()); + assert_bytes(fresh, "unowned fresh attempt"); assert_source_location(id); + TEST_ASSERT_EQUAL_INT(-1, access(temporary_path, F_OK)); + assert_cleanup_pending(&job, "cancelled"); +} +static void test_quarantine_unsupported_preserves_verified_temporary(void) { + storage_migration_job_t job; uint64_t id = create_job(&job, "move"); + quarantine_error = ENOSYS; + TEST_ASSERT_EQUAL_INT(1, storage_migration_process_one()); + assert_source_location(id); assert_bytes(temporary_path, payload); + TEST_ASSERT_EQUAL_INT(-1, access(destination_path, F_OK)); +} +static void test_post_publication_substitution_is_rejected_before_commit(void) { + storage_migration_job_t job; uint64_t id = create_job(&job, "move"); + committing_replacement = 1; + TEST_ASSERT_EQUAL_INT(1, storage_migration_process_one()); + assert_source_location(id); assert_bytes(verified_part, payload); + assert_bytes(destination_path, "substituted published destination"); +} +static void test_restore_collision_retains_private_entry_and_cleanup_debt(void) { + storage_migration_job_t job; uint64_t id = create_job(&job, "move"); + replacement_kind = 3; armed = restore_collision = true; + TEST_ASSERT_EQUAL_INT(1, storage_migration_process_one()); + assert_source_location(id); assert_bytes(verified_part, payload); + assert_bytes(temporary_path, "restore collision sentinel"); + assert_bytes(private_artifact, "unverified replacement data"); + TEST_ASSERT_EQUAL_INT(0, unlink(temporary_path)); /* Remove only test-created sentinel. */ + TEST_ASSERT_EQUAL_INT(0, storage_migration_process_one()); + assert_bytes(private_artifact, "unverified replacement data"); + assert_cleanup_pending(&job, "failed"); +} +static void test_failed_commit_does_not_unlink_substituted_destination(void) { + storage_migration_job_t job; uint64_t id = create_job(&job, "move"); + commit_failure_replacement = true; + TEST_ASSERT_EQUAL_INT(1, storage_migration_process_one()); + assert_source_location(id); assert_bytes(verified_part, payload); + assert_bytes(destination_path, "failed commit substitution sentinel"); +} +static void test_cancelled_publication_does_not_unlink_substituted_destination(void) { + storage_migration_job_t job; uint64_t id = create_job(&job, "move"); + cancel_after_publication = true; + TEST_ASSERT_EQUAL_INT(1, storage_migration_process_one()); + assert_source_location(id); assert_bytes(verified_part, payload); + assert_bytes(destination_path, "cancelled substitution sentinel"); + TEST_ASSERT_EQUAL_INT(0, storage_migration_process_one()); + assert_cleanup_pending(&job, "cancelled"); +} +static void test_modified_verified_inode_is_rejected(void) { + storage_migration_job_t job; uint64_t id = create_job(&job, "move"); + replacement_kind = 4; armed = true; + TEST_ASSERT_EQUAL_INT(1, storage_migration_process_one()); TEST_ASSERT_TRUE(injected); + assert_source_location(id); assert_bytes(temporary_path, "modified verified inode"); + TEST_ASSERT_EQUAL_INT(-1, access(destination_path, F_OK)); +} +static void test_parent_replacement_with_same_file_inode_is_rejected(void) { + storage_migration_job_t job; uint64_t id = create_job(&job, "move"); + committing_replacement = 2; + TEST_ASSERT_EQUAL_INT(1, storage_migration_process_one()); + assert_source_location(id); assert_bytes(destination_path, payload); + TEST_ASSERT_EQUAL_INT(DB_STORAGE_MIGRATION_OK, db_storage_migration_get(job.uuid, &job)); + TEST_ASSERT_EQUAL_STRING("failed", job.state); +} +int main(void) { + strcpy(root, "/tmp/lightnvr-t7-26-XXXXXX"); if (!mkdtemp(root)) return 2; + snprintf(source_root, sizeof(source_root), "%s/source", root); + snprintf(destination_root, sizeof(destination_root), "%s/destination", root); + snprintf(db_path, sizeof(db_path), "%s/database.sqlite", root); + snprintf(relocated_parent, sizeof(relocated_parent), "%s/relocated-parent", root); + snprintf(source_path, sizeof(source_path), "%s/camera/clip.mp4", source_root); + snprintf(destination_path, sizeof(destination_path), "%s/camera/clip.mp4", destination_root); + snprintf(sentinel, sizeof(sentinel), "%s/sentinel", root); + snprintf(verified_part, sizeof(verified_part), "%s/verified-part", root); + load_default_config(&g_config); + if (init_database_ex(db_path, DB_INIT_NO_BACKUP) != 0) { free(g_config.streams); remove_owned_tree(root); return 2; } + UNITY_BEGIN(); + RUN_TEST(test_normal_move_commits_verified_destination_and_removes_source); + RUN_TEST(test_normal_copy_keeps_source_and_verified_replica); + RUN_TEST(test_regular_destination_appearing_after_check_is_not_overwritten); + RUN_TEST(test_symlink_destination_appearing_after_check_is_retained); + RUN_TEST(test_existing_collision_survives_later_abandoned_cleanup); + RUN_TEST(test_unverified_temporary_replacement_is_not_committed); + RUN_TEST(test_unavailable_registered_source_preserves_metadata_and_bytes); + RUN_TEST(test_unsupported_atomic_publication_retains_source_and_temporary); + RUN_TEST(test_unsupported_filesystem_publication_retains_source_and_temporary); + RUN_TEST(test_publication_io_failure_retains_source_and_temporary); + RUN_TEST(test_collision_cleanup_failure_never_overwrites_competing_destination); + RUN_TEST(test_abandoned_cleanup_retains_unowned_symlink); + RUN_TEST(test_cancelled_job_retains_unowned_final_destination); + RUN_TEST(test_cancelled_job_without_artifacts_completes_cleanup); + RUN_TEST(test_matching_bytes_do_not_prove_abandoned_destination_ownership); + RUN_TEST(test_destination_inspection_error_keeps_cleanup_pending); + RUN_TEST(test_preexisting_temporary_is_preserved_while_fresh_attempt_succeeds); + RUN_TEST(test_preexisting_temporary_survives_failed_job_and_later_cleanup); + RUN_TEST(test_retry_preserves_previous_attempt_and_uses_fresh_artifact); + RUN_TEST(test_preexisting_temporary_symlink_and_target_survive); + RUN_TEST(test_fresh_attempt_without_legacy_name_keeps_cleanup_pending); + RUN_TEST(test_quarantine_unsupported_preserves_verified_temporary); + RUN_TEST(test_post_publication_substitution_is_rejected_before_commit); + RUN_TEST(test_restore_collision_retains_private_entry_and_cleanup_debt); + RUN_TEST(test_failed_commit_does_not_unlink_substituted_destination); + RUN_TEST(test_cancelled_publication_does_not_unlink_substituted_destination); + RUN_TEST(test_modified_verified_inode_is_rejected); + RUN_TEST(test_parent_replacement_with_same_file_inode_is_rejected); + int result = UNITY_END(); shutdown_database(); free(g_config.streams); g_config.streams = NULL; + remove_owned_tree(root); return result; +} diff --git a/tests/unit/test_t7_27.c b/tests/unit/test_t7_27.c new file mode 100644 index 000000000..ea547d6b2 --- /dev/null +++ b/tests/unit/test_t7_27.c @@ -0,0 +1,456 @@ +#define _GNU_SOURCE +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "unity.h" +#include "core/config.h" +#include "core/path_utils.h" +#include "database/db_core.h" +#include "database/db_operator_floor_plans.h" +#include "utils/strings.h" +#include "web/api_handlers_operator_floor_plans.h" +#include "web/request_response.h" + +static char root[128], storage[256], database[256]; +static char path[512], backup[520], saved[256], sentinel[256], uploaded[256]; +static char endpoint[128], uuid[CAMERA_UUID_STRING_SIZE]; +static char original_storage[MAX_PATH_LENGTH]; +static bool armed_access, injected, fail_publication, publication_competitor; +static bool fail_metadata, metadata_competitor; +static int replacement_kind, binding_error, publication_race_kind, publication_error, restore_error; +static char observed_upload[1024], observed_quarantine[1024]; +static int recovery_error; +static bool recovery_restore_collision, replace_upload_directory; +static char displaced_private[256], replaced_private[1024]; +static bool fail_backup_creation, tamper_backup_directory, mismatch_restore_collision; +static char altered_directory[1024]; +static char observed_backup[1024]; +static const unsigned char old_png[] = {0x89,'P','N','G',13,10,26,10,'o','l','d'}; +static const unsigned char new_png[] = {0x89,'P','N','G',13,10,26,10,'n','e','w'}; + +static void put_bytes(const char *name, const void *data, size_t size) { + TEST_ASSERT_EQUAL_INT(0, ensure_path(name)); + FILE *file = fopen(name, "wb"); TEST_ASSERT_NOT_NULL(file); + size_t count = fwrite(data, 1, size, file); int result = fclose(file); + TEST_ASSERT_EQUAL_UINT(size, count); TEST_ASSERT_EQUAL_INT(0, result); +} +static void put_text(const char *name, const char *text) { put_bytes(name, text, strlen(text)); } +static void assert_bytes(const char *name, const void *expected, size_t size) { + unsigned char data[128]; FILE *file = fopen(name, "rb"); TEST_ASSERT_NOT_NULL(file); + size_t count = fread(data, 1, sizeof(data), file); int result = fclose(file); + TEST_ASSERT_EQUAL_INT(0, result); TEST_ASSERT_EQUAL_UINT(size, count); + TEST_ASSERT_EQUAL_MEMORY(expected, data, size); +} +static void assert_text(const char *name, const char *text) { assert_bytes(name, text, strlen(text)); } +static bool before_upload_publication(void) { + if (publication_race_kind) { + injected = true; + if (publication_race_kind == 1) put_text(path, "publication competitor"); + else TEST_ASSERT_EQUAL_INT(0, symlink(sentinel, path)); + publication_race_kind = 0; + } + if (fail_publication || publication_error) { + fail_publication = false; injected = true; + if (publication_competitor) put_text(path, "publication rollback competitor"); + errno = publication_error ? publication_error : ENOSPC; + return false; + } + return true; +} +int __real_access(const char *, int); +int __real_rename(const char *, const char *); +static void replace_inspected_background(void) { + armed_access = false; injected = true; + TEST_ASSERT_EQUAL_INT(0, __real_rename(path, saved)); + if (replacement_kind == 1) put_text(path, "unrelated replacement"); + else TEST_ASSERT_EQUAL_INT(0, symlink(sentinel, path)); +} +int __wrap_access(const char *name, int mode) { + int result = __real_access(name, mode); int saved_errno = errno; + if (armed_access && !strcmp(name, path) && result == 0) replace_inspected_background(); + errno = saved_errno; return result; +} +int __real_fstat(int, struct stat *); +int __wrap_fstat(int fd, struct stat *info) { + int result = __real_fstat(fd, info); int saved_errno = errno; + if (armed_access && result == 0 && S_ISREG(info->st_mode)) { + char proc[64], name[1024]; snprintf(proc, sizeof(proc), "/proc/self/fd/%d", fd); + ssize_t size = readlink(proc, name, sizeof(name)-1); + if (size > 0) { name[size] = 0; if (!strcmp(name, path)) replace_inspected_background(); } + } + errno = saved_errno; return result; +} +long __real_syscall(long, ...); +long __wrap_syscall(long number, ...) { + if (number != SYS_renameat2) { TEST_FAIL_MESSAGE("Unexpected background syscall"); errno = ENOSYS; return -1; } + va_list args; va_start(args, number); + int from = va_arg(args, int); const char *oldname = va_arg(args, const char *); + int to = va_arg(args, int); const char *newname = va_arg(args, const char *); + unsigned flags = va_arg(args, unsigned); va_end(args); + if (!strcmp(newname, "original")) { + char proc[64], name[1024]; snprintf(proc, sizeof(proc), "/proc/self/fd/%d", to); + ssize_t size = readlink(proc, name, sizeof(name)-1); TEST_ASSERT_TRUE(size > 0); name[size] = 0; + int length = snprintf(observed_backup, sizeof(observed_backup), "%s/original", name); + TEST_ASSERT_TRUE(length > 0 && (size_t)length < sizeof(observed_backup)); + if (binding_error) { errno = binding_error; return -1; } + } + if (strstr(oldname, ".tmp.") && !strcmp(newname, strrchr(path, '/') + 1)) { + char proc[64], directory[1024]; snprintf(proc, sizeof(proc), "/proc/self/fd/%d", from); + ssize_t size = readlink(proc, directory, sizeof(directory)-1); TEST_ASSERT_TRUE(size > 0); directory[size] = 0; + int length = snprintf(observed_upload, sizeof(observed_upload), "%s/%s", directory, oldname); + TEST_ASSERT_TRUE(length > 0 && (size_t)length < sizeof(observed_upload)); + if (!before_upload_publication()) return -1; + } + if (!strcmp(newname, "failed-upload")) { + char proc[64], directory[1024]; snprintf(proc, sizeof(proc), "/proc/self/fd/%d", to); + ssize_t size = readlink(proc, directory, sizeof(directory)-1); TEST_ASSERT_TRUE(size > 0); directory[size] = 0; + int length = snprintf(observed_quarantine, sizeof(observed_quarantine), "%s/failed-upload", directory); + TEST_ASSERT_TRUE(length > 0 && (size_t)length < sizeof(observed_quarantine)); + if (recovery_error) { errno = recovery_error; return -1; } + } + if (recovery_restore_collision && !strcmp(oldname, "failed-upload")) + put_text(path, "recovery restore competitor"); + if (restore_error && !strcmp(oldname, "original") && strcmp(newname, "original")) { + errno = restore_error; return -1; + } + if (mismatch_restore_collision && !strcmp(oldname, "original") && strcmp(newname, "original")) + put_text(path, "identity restore competitor"); + return __real_syscall(number, from, oldname, to, newname, flags); +} +static void substitute_upload_directory(const char *directory) { + replace_upload_directory = false; injected = true; + safe_strcpy(replaced_private, directory, sizeof(replaced_private), 0); + TEST_ASSERT_EQUAL_INT(0, __real_rename(directory, displaced_private)); + TEST_ASSERT_EQUAL_INT(0, mkdir(directory, 0700)); + char marker[1200]; int length = snprintf(marker, sizeof(marker), "%s/marker", directory); + TEST_ASSERT_TRUE(length > 0 && (size_t)length < sizeof(marker)); + put_text(marker, "replacement directory marker"); +} +int __real_mkstemp(char *); +int __wrap_mkstemp(char *name) { + if (replace_upload_directory && strstr(name, "/upload.tmp.")) { + char directory[1024]; safe_strcpy(directory, name, sizeof(directory), 0); + *strrchr(directory, '/') = 0; substitute_upload_directory(directory); + } + return __real_mkstemp(name); +} +int __real_openat(int, const char *, int, ...); +int __wrap_openat(int fd, const char *name, int flags, ...) { + mode_t mode = 0; + if (flags & O_CREAT) { va_list args; va_start(args, flags); mode = (mode_t)va_arg(args, int); va_end(args); } + if (replace_upload_directory && !strncmp(name, "upload.tmp.", 11) && (flags & O_CREAT)) { + char proc[64], directory[1024]; snprintf(proc, sizeof(proc), "/proc/self/fd/%d", fd); + ssize_t size = readlink(proc, directory, sizeof(directory)-1); TEST_ASSERT_TRUE(size > 0); directory[size] = 0; + substitute_upload_directory(directory); + } + return flags & O_CREAT ? __real_openat(fd, name, flags, mode) : __real_openat(fd, name, flags); +} +char *__real_mkdtemp(char *); +char *__wrap_mkdtemp(char *name) { + if (fail_backup_creation && strstr(name, ".backup.")) { errno = EACCES; return NULL; } + char *result = __real_mkdtemp(name); + if (result && tamper_backup_directory && strstr(name, ".backup.")) { + TEST_ASSERT_EQUAL_INT(0, chmod(name, 0755)); + safe_strcpy(altered_directory, name, sizeof(altered_directory), 0); + } + return result; +} +int __wrap_rename(const char *from, const char *to) { + if (!strcmp(from, path) && (strstr(to, ".bak") || strstr(to, "/original"))) + safe_strcpy(observed_backup, to, sizeof(observed_backup), 0); + if (!strcmp(to, path) && strstr(from, ".tmp.")) { + safe_strcpy(observed_upload, from, sizeof(observed_upload), 0); + if (!before_upload_publication()) return -1; + } + if (restore_error && !strcmp(from, observed_backup) && !strcmp(to, path)) { + errno = restore_error; return -1; + } + return __real_rename(from, to); +} +db_operator_floor_plan_result_t __real_db_operator_floor_plan_set_background(const char *, const char *); +db_operator_floor_plan_result_t __wrap_db_operator_floor_plan_set_background(const char *id, const char *mime) { + if (fail_metadata && mime && !strcmp(id, uuid)) { + fail_metadata = false; injected = true; + if (metadata_competitor) { + TEST_ASSERT_EQUAL_INT(0, __real_rename(path, uploaded)); + put_text(path, "metadata rollback competitor"); + } + return DB_OPERATOR_FLOOR_PLAN_ERROR; + } + return __real_db_operator_floor_plan_set_background(id, mime); +} +static void remove_owned_tree(const char *directory) { + DIR *dir = opendir(directory); if (!dir) return; + struct dirent *entry; + while ((entry = readdir(dir))) { + if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue; + char name[1024]; int size = snprintf(name, sizeof(name), "%s/%s", directory, entry->d_name); + if (size < 0 || (size_t)size >= sizeof(name)) continue; + struct stat info; + if (lstat(name, &info) == 0 && S_ISDIR(info.st_mode)) remove_owned_tree(name); + else unlink(name); + } + closedir(dir); rmdir(directory); +} +static int upload_background(void) { + http_request_t request; http_response_t response; + http_request_init(&request); http_response_init(&response); + request.method = HTTP_METHOD_PUT; + safe_strcpy(request.path, endpoint, sizeof(request.path), 0); + strcpy(request.client_ip, "127.0.0.1"); + request.body = (void *)new_png; request.body_len = sizeof(new_png); + strcpy(request.headers[0].name, "Content-Type"); + strcpy(request.headers[0].value, "image/png"); request.num_headers = 1; + handle_put_operator_floor_plan_background(&request, &response); + int status = response.status_code; + http_response_free(&response); /* Release before assertions can longjmp. */ + return status; +} +static void assert_metadata_unchanged(void) { + operator_floor_plan_t plan; + TEST_ASSERT_EQUAL_INT(DB_OPERATOR_FLOOR_PLAN_OK, db_operator_floor_plan_get(uuid, &plan)); + TEST_ASSERT_EQUAL_STRING("image/png", plan.background_mime); + TEST_ASSERT_EQUAL_INT64(1, plan.revision); +} +void setUp(void) { + armed_access = injected = fail_publication = publication_competitor = false; + fail_metadata = metadata_competitor = fail_backup_creation = false; replacement_kind = binding_error = publication_race_kind = publication_error = restore_error = 0; observed_upload[0] = observed_quarantine[0] = 0; recovery_error = 0; recovery_restore_collision = replace_upload_directory = false; replaced_private[0] = 0; observed_backup[0] = 0; tamper_backup_directory = mismatch_restore_collision = false; altered_directory[0] = 0; + g_config.web_auth_enabled = false; g_config.demo_mode = false; + safe_strcpy(g_config.storage_path, storage, sizeof(g_config.storage_path), 0); + TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_exec(get_db_handle(), "DELETE FROM operator_floor_plans;", NULL, NULL, NULL)); + remove_owned_tree(storage); remove_owned_tree(displaced_private); TEST_ASSERT_EQUAL_INT(0, mkdir(storage, 0700)); + unlink(saved); unlink(sentinel); unlink(uploaded); + operator_floor_plan_t plan = {0}; + strcpy(plan.name, "T7.27 private plan"); plan.canvas_width = 1000; plan.canvas_height = 700; + TEST_ASSERT_EQUAL_INT(DB_OPERATOR_FLOOR_PLAN_OK, db_operator_floor_plan_create(&plan, NULL, 0, NULL)); + strcpy(uuid, plan.uuid); + TEST_ASSERT_EQUAL_INT(DB_OPERATOR_FLOOR_PLAN_OK, db_operator_floor_plan_set_background(uuid, "image/png")); + snprintf(path, sizeof(path), "%s/floor_plans/%s.png", storage, uuid); + snprintf(backup, sizeof(backup), "%s.bak", path); + snprintf(endpoint, sizeof(endpoint), "/api/live/plans/%s/background", uuid); + put_bytes(path, old_png, sizeof(old_png)); put_text(sentinel, "sentinel unchanged"); +} +void tearDown(void) { + armed_access = fail_publication = fail_metadata = false; + g_config.web_auth_enabled = false; + safe_strcpy(g_config.storage_path, original_storage, sizeof(g_config.storage_path), 0); +} +static void test_normal_same_type_replacement_updates_bytes(void) { + TEST_ASSERT_EQUAL_INT(200, upload_background()); + assert_bytes(path, new_png, sizeof(new_png)); assert_metadata_unchanged(); + TEST_ASSERT_EQUAL_INT(-1, access(backup, F_OK)); +} +static void test_publication_failure_restores_original_without_competitor(void) { + fail_publication = true; + TEST_ASSERT_EQUAL_INT(500, upload_background()); TEST_ASSERT_TRUE(injected); + assert_bytes(path, old_png, sizeof(old_png)); assert_metadata_unchanged(); +} +static void test_metadata_failure_restores_original_without_competitor(void) { + fail_metadata = true; + TEST_ASSERT_EQUAL_INT(500, upload_background()); TEST_ASSERT_TRUE(injected); + assert_bytes(path, old_png, sizeof(old_png)); assert_metadata_unchanged(); +} +static void test_replacement_after_access_is_not_backed_up_and_deleted(void) { + armed_access = true; replacement_kind = 1; + int status = upload_background(); TEST_ASSERT_TRUE(injected); + assert_bytes(saved, old_png, sizeof(old_png)); + assert_text(path, "unrelated replacement"); + TEST_ASSERT_TRUE(status >= 400); assert_metadata_unchanged(); +} +static void test_symlink_after_access_is_preserved_without_touching_target(void) { + armed_access = true; replacement_kind = 2; + int status = upload_background(); TEST_ASSERT_TRUE(injected); + assert_text(sentinel, "sentinel unchanged"); assert_bytes(saved, old_png, sizeof(old_png)); + struct stat info; TEST_ASSERT_EQUAL_INT(0, lstat(path, &info)); TEST_ASSERT_TRUE(S_ISLNK(info.st_mode)); + TEST_ASSERT_TRUE(status >= 400); assert_metadata_unchanged(); +} +static void test_publication_rollback_does_not_overwrite_competitor(void) { + fail_publication = publication_competitor = true; + TEST_ASSERT_EQUAL_INT(500, upload_background()); TEST_ASSERT_TRUE(injected); + assert_text(path, "publication rollback competitor"); + TEST_ASSERT_TRUE(observed_backup[0] != 0); + assert_bytes(observed_backup, old_png, sizeof(old_png)); assert_metadata_unchanged(); +} +static void test_metadata_rollback_does_not_unlink_competitor(void) { + fail_metadata = metadata_competitor = true; + TEST_ASSERT_EQUAL_INT(500, upload_background()); TEST_ASSERT_TRUE(injected); + assert_text(path, "metadata rollback competitor"); + TEST_ASSERT_TRUE(observed_backup[0] != 0); + assert_bytes(observed_backup, old_png, sizeof(old_png)); + assert_bytes(uploaded, new_png, sizeof(new_png)); assert_metadata_unchanged(); +} +static void test_preexisting_backup_is_not_silently_deleted(void) { + put_text(backup, "unknown preexisting backup"); + int status = upload_background(); + assert_text(backup, "unknown preexisting backup"); + TEST_ASSERT_TRUE(status == 200 || status >= 400); +} +static void test_unauthenticated_request_preserves_existing_file(void) { + g_config.web_auth_enabled = true; + TEST_ASSERT_EQUAL_INT(401, upload_background()); + assert_bytes(path, old_png, sizeof(old_png)); assert_metadata_unchanged(); +} +static void test_preexisting_backup_symlink_is_preserved(void) { + TEST_ASSERT_EQUAL_INT(0, symlink(sentinel, backup)); + TEST_ASSERT_EQUAL_INT(200, upload_background()); + struct stat info; TEST_ASSERT_EQUAL_INT(0, lstat(backup, &info)); + TEST_ASSERT_TRUE(S_ISLNK(info.st_mode)); assert_text(sentinel, "sentinel unchanged"); + assert_bytes(path, new_png, sizeof(new_png)); +} +static void test_private_backup_creation_failure_preserves_old_file(void) { + fail_backup_creation = true; + TEST_ASSERT_EQUAL_INT(500, upload_background()); + assert_bytes(path, old_png, sizeof(old_png)); assert_metadata_unchanged(); +} +static void test_invalid_private_directory_is_retained_without_touching_background(void) { + tamper_backup_directory = true; + TEST_ASSERT_EQUAL_INT(500, upload_background()); + assert_bytes(path, old_png, sizeof(old_png)); assert_metadata_unchanged(); + struct stat info; TEST_ASSERT_TRUE(altered_directory[0] != 0); + TEST_ASSERT_EQUAL_INT(0, lstat(altered_directory, &info)); + TEST_ASSERT_TRUE(S_ISDIR(info.st_mode)); +} +static void test_initial_upload_without_old_background_succeeds(void) { + TEST_ASSERT_EQUAL_INT(0, unlink(path)); + TEST_ASSERT_EQUAL_INT(DB_OPERATOR_FLOOR_PLAN_OK, db_operator_floor_plan_set_background(uuid, NULL)); + TEST_ASSERT_EQUAL_INT(200, upload_background()); + assert_bytes(path, new_png, sizeof(new_png)); assert_metadata_unchanged(); +} +static void test_preexisting_symlink_background_is_rejected(void) { + TEST_ASSERT_EQUAL_INT(0, unlink(path)); TEST_ASSERT_EQUAL_INT(0, symlink(sentinel, path)); + TEST_ASSERT_EQUAL_INT(500, upload_background()); + struct stat info; TEST_ASSERT_EQUAL_INT(0, lstat(path, &info)); TEST_ASSERT_TRUE(S_ISLNK(info.st_mode)); + assert_text(sentinel, "sentinel unchanged"); assert_metadata_unchanged(); +} +static void test_unsupported_old_file_binding_preserves_background(void) { + binding_error = ENOSYS; + TEST_ASSERT_EQUAL_INT(500, upload_background()); + assert_bytes(path, old_png, sizeof(old_png)); assert_metadata_unchanged(); +} +static void test_identity_restore_collision_preserves_all_entries(void) { + armed_access = true; replacement_kind = 1; mismatch_restore_collision = true; + TEST_ASSERT_EQUAL_INT(500, upload_background()); TEST_ASSERT_TRUE(injected); + assert_bytes(saved, old_png, sizeof(old_png)); + assert_text(path, "identity restore competitor"); + assert_text(observed_backup, "unrelated replacement"); assert_metadata_unchanged(); +} +static void test_upload_publication_does_not_overwrite_new_competitor(void) { + publication_race_kind = 1; + TEST_ASSERT_EQUAL_INT(500, upload_background()); TEST_ASSERT_TRUE(injected); + assert_text(path, "publication competitor"); + assert_bytes(observed_backup, old_png, sizeof(old_png)); + assert_bytes(observed_upload, new_png, sizeof(new_png)); assert_metadata_unchanged(); +} +static void test_upload_publication_preserves_competing_symlink(void) { + publication_race_kind = 2; + TEST_ASSERT_EQUAL_INT(500, upload_background()); TEST_ASSERT_TRUE(injected); + struct stat info; TEST_ASSERT_EQUAL_INT(0, lstat(path, &info)); TEST_ASSERT_TRUE(S_ISLNK(info.st_mode)); + assert_text(sentinel, "sentinel unchanged"); + assert_bytes(observed_backup, old_png, sizeof(old_png)); assert_metadata_unchanged(); +} +static void test_unsupported_publication_restores_old_background(void) { + publication_error = ENOSYS; + TEST_ASSERT_EQUAL_INT(500, upload_background()); TEST_ASSERT_TRUE(injected); + assert_bytes(path, old_png, sizeof(old_png)); assert_metadata_unchanged(); +} +static void test_unsupported_restore_retains_original_private_backup(void) { + fail_publication = true; restore_error = ENOSYS; + TEST_ASSERT_EQUAL_INT(500, upload_background()); TEST_ASSERT_TRUE(injected); + TEST_ASSERT_EQUAL_INT(-1, access(path, F_OK)); + assert_bytes(observed_backup, old_png, sizeof(old_png)); assert_metadata_unchanged(); +} +static void test_failed_recovery_preserves_upload_and_original(void) { + fail_metadata = true; recovery_error = ENOSYS; + TEST_ASSERT_EQUAL_INT(500, upload_background()); TEST_ASSERT_TRUE(injected); + assert_bytes(path, new_png, sizeof(new_png)); + assert_bytes(observed_backup, old_png, sizeof(old_png)); assert_metadata_unchanged(); +} +static void test_changed_upload_restore_collision_preserves_every_entry(void) { + fail_metadata = metadata_competitor = recovery_restore_collision = true; + TEST_ASSERT_EQUAL_INT(500, upload_background()); TEST_ASSERT_TRUE(injected); + assert_text(path, "recovery restore competitor"); + assert_text(observed_quarantine, "metadata rollback competitor"); + assert_bytes(observed_backup, old_png, sizeof(old_png)); + assert_bytes(uploaded, new_png, sizeof(new_png)); assert_metadata_unchanged(); +} +static int descriptor_count(void) { + DIR *directory = opendir("/proc/self/fd"); TEST_ASSERT_NOT_NULL(directory); + int count = 0; struct dirent *entry; + while ((entry = readdir(directory))) if (strcmp(entry->d_name, ".") && strcmp(entry->d_name, "..")) ++count; + closedir(directory); return count; +} +static void test_repeated_ordinary_failure_paths_release_descriptors(void) { + int before = descriptor_count(); + for (int index = 0; index < 9; ++index) { + binding_error = index % 3 == 0 ? ENOSYS : 0; + publication_error = index % 3 == 1 ? ENOSYS : 0; + fail_metadata = index % 3 == 2; + TEST_ASSERT_EQUAL_INT(500, upload_background()); + assert_bytes(path, old_png, sizeof(old_png)); assert_metadata_unchanged(); + } + TEST_ASSERT_EQUAL_INT(before, descriptor_count()); +} +static void test_upload_creation_uses_held_private_directory(void) { + replace_upload_directory = true; + TEST_ASSERT_EQUAL_INT(500, upload_background()); TEST_ASSERT_TRUE(injected); + assert_bytes(path, old_png, sizeof(old_png)); assert_metadata_unchanged(); + DIR *directory = opendir(replaced_private); TEST_ASSERT_NOT_NULL(directory); + int unexpected = 0; struct dirent *entry; + while ((entry = readdir(directory))) + if (strcmp(entry->d_name, ".") && strcmp(entry->d_name, "..") && strcmp(entry->d_name, "marker")) ++unexpected; + closedir(directory); + TEST_ASSERT_EQUAL_INT(0, unexpected); + char marker[1200]; snprintf(marker, sizeof(marker), "%s/marker", replaced_private); + assert_text(marker, "replacement directory marker"); +} +int main(void) { + strcpy(root, "/tmp/lightnvr-t7-27-XXXXXX"); if (!mkdtemp(root)) return 2; + snprintf(storage, sizeof(storage), "%s/storage", root); + snprintf(database, sizeof(database), "%s/database.sqlite", root); + snprintf(saved, sizeof(saved), "%s/original", root); + snprintf(uploaded, sizeof(uploaded), "%s/uploaded", root); + snprintf(displaced_private, sizeof(displaced_private), "%s/displaced-private", root); + snprintf(sentinel, sizeof(sentinel), "%s/sentinel", root); + load_default_config(&g_config); + safe_strcpy(original_storage, g_config.storage_path, sizeof(original_storage), 0); + if (init_database_ex(database, DB_INIT_NO_BACKUP) != 0) { + free(g_config.streams); remove_owned_tree(root); return 2; + } + UNITY_BEGIN(); + RUN_TEST(test_normal_same_type_replacement_updates_bytes); + RUN_TEST(test_publication_failure_restores_original_without_competitor); + RUN_TEST(test_metadata_failure_restores_original_without_competitor); + RUN_TEST(test_replacement_after_access_is_not_backed_up_and_deleted); + RUN_TEST(test_symlink_after_access_is_preserved_without_touching_target); + RUN_TEST(test_publication_rollback_does_not_overwrite_competitor); + RUN_TEST(test_metadata_rollback_does_not_unlink_competitor); + RUN_TEST(test_preexisting_backup_is_not_silently_deleted); + RUN_TEST(test_unauthenticated_request_preserves_existing_file); + RUN_TEST(test_preexisting_backup_symlink_is_preserved); + RUN_TEST(test_private_backup_creation_failure_preserves_old_file); + RUN_TEST(test_invalid_private_directory_is_retained_without_touching_background); + RUN_TEST(test_initial_upload_without_old_background_succeeds); + RUN_TEST(test_preexisting_symlink_background_is_rejected); + RUN_TEST(test_unsupported_old_file_binding_preserves_background); + RUN_TEST(test_identity_restore_collision_preserves_all_entries); + RUN_TEST(test_upload_publication_does_not_overwrite_new_competitor); + RUN_TEST(test_upload_publication_preserves_competing_symlink); + RUN_TEST(test_unsupported_publication_restores_old_background); + RUN_TEST(test_unsupported_restore_retains_original_private_backup); + RUN_TEST(test_failed_recovery_preserves_upload_and_original); + RUN_TEST(test_changed_upload_restore_collision_preserves_every_entry); + RUN_TEST(test_repeated_ordinary_failure_paths_release_descriptors); + RUN_TEST(test_upload_creation_uses_held_private_directory); + int result = UNITY_END(); + shutdown_database(); free(g_config.streams); g_config.streams = NULL; + remove_owned_tree(root); return result; +} diff --git a/tests/unit/test_t7_28.c b/tests/unit/test_t7_28.c new file mode 100644 index 000000000..4888a87dc --- /dev/null +++ b/tests/unit/test_t7_28.c @@ -0,0 +1,275 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include +#include +#include "unity.h" +#include "core/event_router.h" +#include "database/db_event_route_suppression.h" + +#define FIXTURE_ZONE "/usr/share/zoneinfo/Etc/T7_28" +#define MAX_TRANSITIONS 4096U /* Existing production contract, not a new limit. */ +#define MAX_FILE_BYTES (1024U * 1024U) +#define EVENT_TYPE "io.lightnvr.camera.offline.v1" +static char directory[128], fixture[256]; +static event_route_t route; +static event_envelope_t event; +static event_route_delivery_plan_t plan; +static uint64_t generation = 1; +static bool monitor; +static unsigned transition_calls, oversized_calls, file_buffer_calls; +static unsigned fail_transition_call; +static size_t allocation_counts[2], allocation_widths[2]; +static void *transition_blocks[2]; +static int live_blocks; + +int __wrap_db_event_route_count(void) { return 1; } +int __wrap_db_event_route_list(event_route_t *output, int maximum) { + if (maximum < 1) return -1; + *output = route; + return 1; +} +uint64_t __wrap_db_event_route_generation(void) { return generation; } +uint64_t __wrap_db_event_destination_generation(void) { return 1; } +int __wrap_db_event_route_suppression_prune(int64_t before, int maximum, int *deleted) { + (void)before; (void)maximum; *deleted = 0; return 0; +} +FILE *__real_fopen(const char *, const char *); +FILE *__wrap_fopen(const char *name, const char *mode) { + return __real_fopen(strcmp(name, FIXTURE_ZONE) == 0 ? fixture : name, mode); +} +int __real_stat(const char *, struct stat *); +int __wrap_stat(const char *name, struct stat *info) { + return __real_stat(strcmp(name, FIXTURE_ZONE) == 0 ? fixture : name, info); +} +void *__real_calloc(size_t, size_t); +void *__wrap_calloc(size_t count, size_t width) { + if (monitor && (width == sizeof(int64_t) || width == sizeof(unsigned char))) { + unsigned index = transition_calls++; + if (count > MAX_TRANSITIONS || count > SIZE_MAX / width) { + oversized_calls++; + return NULL; /* Never attempt an unbounded allocation in a probe. */ + } + if (index < 2) { allocation_counts[index] = count; allocation_widths[index] = width; } + if (fail_transition_call == index + 1) return NULL; + void *result = __real_calloc(count, width); + if (result && index < 2) { transition_blocks[index] = result; live_blocks++; } + return result; + } + return __real_calloc(count, width); +} +void *__real_malloc(size_t); +void *__wrap_malloc(size_t size) { + if (monitor && size >= MAX_FILE_BYTES) { + file_buffer_calls++; + if (size > MAX_FILE_BYTES) { oversized_calls++; return NULL; } + } + return __real_malloc(size); +} +void __real_free(void *); +void __wrap_free(void *memory) { + if (memory) for (unsigned i = 0; i < 2; i++) { + if (transition_blocks[i] == memory) { transition_blocks[i] = NULL; live_blocks--; } + } + __real_free(memory); +} +static void be32(FILE *file, uint32_t value) { + unsigned char bytes[] = {(unsigned char)(value >> 24), (unsigned char)(value >> 16), + (unsigned char)(value >> 8), (unsigned char)value}; + TEST_ASSERT_EQUAL_UINT(4, fwrite(bytes, 1, 4, file)); +} +static void header(FILE *file, char version, uint32_t transitions, uint32_t types, + uint32_t leaps, uint32_t characters) { + unsigned char prefix[20] = {'T','Z','i','f',0}; prefix[4] = (unsigned char)version; + TEST_ASSERT_EQUAL_UINT(sizeof(prefix), fwrite(prefix, 1, sizeof(prefix), file)); + be32(file, 0); be32(file, 0); be32(file, leaps); + be32(file, transitions); be32(file, types); be32(file, characters); +} +static void block(FILE *file, uint32_t count, uint32_t types, unsigned width, + unsigned char index) { + /* Invalid raw counts deliberately have no oversized backing body. */ + uint32_t actual_count = count <= MAX_TRANSITIONS ? count : 0; + uint32_t actual_types = types <= 256 ? types : 0; + for (uint32_t i = 0; i < actual_count; i++) { + if (width == 8) be32(file, 0); + be32(file, i); + } + for (uint32_t i = 0; i < actual_count; i++) fputc(index, file); + for (uint32_t i = 0; i < actual_types; i++) { + be32(file, 0); fputc(0, file); fputc(0, file); + } + TEST_ASSERT_EQUAL_UINT(4, fwrite("UTC", 1, 4, file)); +} +static void write_fixture(uint32_t count, uint32_t types, bool extended, + unsigned char index) { + FILE *file = fopen(fixture, "wb"); TEST_ASSERT_NOT_NULL(file); + if (extended) { + header(file, '2', 0, 1, 0, 4); block(file, 0, 1, 4, 0); + } + header(file, extended ? '2' : '\0', count, types, 0, 4); + block(file, count, types, extended ? 8 : 4, index); + TEST_ASSERT_EQUAL_INT(0, fclose(file)); +} +void setUp(void) { + event_router_shutdown(); + memset(&plan, 0, sizeof(plan)); memset(&event, 0, sizeof(event)); + monitor = false; transition_calls = oversized_calls = file_buffer_calls = 0; + fail_transition_call = 0; live_blocks = 0; + memset(transition_blocks, 0, sizeof(transition_blocks)); + memset(allocation_counts, 0, sizeof(allocation_counts)); + memset(allocation_widths, 0, sizeof(allocation_widths)); + strcpy(directory, "/tmp/lightnvr_t7_28_XXXXXX"); TEST_ASSERT_NOT_NULL(mkdtemp(directory)); + snprintf(fixture, sizeof(fixture), "%s/timezone", directory); + write_fixture(1, 1, false, 0); + memset(&route, 0, sizeof(route)); + strcpy(route.uuid, "11111111-1111-4111-8111-111111111111"); + strcpy(route.name, "TZif boundary"); route.enabled = true; route.revision = 1; + strcpy(route.destination_key, EVENT_ROUTE_DEFAULT_DESTINATION); + strcpy(route.scope_type, "all"); strcpy(route.predicate_json, "{\"version\":1}"); + strcpy(route.event_types[0], EVENT_TYPE); route.event_type_count = 1; + strcpy(route.schedule_json, "{\"version\":1,\"timezone\":\"Etc/T7_28\",\"windows\":[{\"days\":[0,1,2,3,4,5,6],\"start\":\"00:00\",\"end\":\"23:59\"}]}"); + generation++; + cJSON *data = cJSON_CreateObject(); TEST_ASSERT_NOT_NULL(data); + cJSON_AddStringToObject(data, "stream_name", "camera"); + cJSON_AddStringToObject(data, "reason", "fixture"); + cJSON_AddNumberToObject(data, "consecutive_failures", 1); + int result = event_envelope_create(&event, EVENT_TYPE, + "urn:lightnvr:11111111-1111-4111-8111-111111111111", + "camera/22222222-2222-4222-8222-222222222222", 1786991400, + data, NULL, 0); + cJSON_Delete(data); TEST_ASSERT_EQUAL_INT(0, result); +} +void tearDown(void) { + monitor = false; + event_route_delivery_plan_clear(&plan); event_envelope_clear(&event); + event_router_shutdown(); + unlink(fixture); rmdir(directory); + TEST_ASSERT_EQUAL_INT(0, live_blocks); +} +static void evaluate(event_router_result_t expected) { + event_route_t before = route; + event_envelope_t original_event = event; + monitor = true; + event_router_result_t result = event_router_evaluate_delivery(&event, &plan); + monitor = false; + TEST_ASSERT_EQUAL_INT(expected, result); + TEST_ASSERT_EQUAL_MEMORY(&before, &route, sizeof(route)); + TEST_ASSERT_EQUAL_MEMORY(&original_event, &event, sizeof(event)); + TEST_ASSERT_EQUAL_UINT(0, oversized_calls); + if (expected == EVENT_ROUTER_MATCH) { + TEST_ASSERT_EQUAL_UINT(1, plan.count); + TEST_ASSERT_EQUAL_STRING(EVENT_ROUTE_DEFAULT_DESTINATION, plan.entries[0].destination_key); + TEST_ASSERT_EQUAL_STRING(route.uuid, plan.entries[0].route_uuid); + } else { + TEST_ASSERT_EQUAL_UINT(0, plan.count); TEST_ASSERT_NULL(plan.entries); + } +} +static void reject_without_transition_allocation(void) { + evaluate(EVENT_ROUTER_ERROR); + TEST_ASSERT_EQUAL_UINT(0, transition_calls); TEST_ASSERT_EQUAL_INT(0, live_blocks); +} +static void test_ordinary_delivery_allocates_two_bounded_arrays(void) { + evaluate(EVENT_ROUTER_MATCH); + TEST_ASSERT_EQUAL_UINT(2, transition_calls); + TEST_ASSERT_EQUAL_UINT(1, allocation_counts[0]); TEST_ASSERT_EQUAL_UINT(8, allocation_widths[0]); + TEST_ASSERT_EQUAL_UINT(1, allocation_counts[1]); TEST_ASSERT_EQUAL_UINT(1, allocation_widths[1]); +} +static void test_zero_transitions_needs_no_arrays(void) { + write_fixture(0, 1, false, 0); evaluate(EVENT_ROUTER_MATCH); + TEST_ASSERT_EQUAL_UINT(0, transition_calls); +} +static void test_maximum_transitions_retains_delivery(void) { + write_fixture(MAX_TRANSITIONS, 1, false, 0); evaluate(EVENT_ROUTER_MATCH); + TEST_ASSERT_EQUAL_UINT(2, transition_calls); + TEST_ASSERT_EQUAL_UINT(MAX_TRANSITIONS, allocation_counts[0]); + TEST_ASSERT_EQUAL_UINT(MAX_TRANSITIONS, allocation_counts[1]); +} +static void test_one_past_transition_limit_rejected_before_arrays(void) { + write_fixture(MAX_TRANSITIONS + 1, 1, false, 0); reject_without_transition_allocation(); +} +static void test_maximum_raw_count_rejected_before_arrays(void) { + write_fixture(UINT32_MAX, 1, false, 0); reject_without_transition_allocation(); +} +static void test_signed_boundary_count_rejected_before_arrays(void) { + write_fixture((uint32_t)INT_MAX + 1U, 1, false, 0); reject_without_transition_allocation(); +} +static void test_zero_types_rejected_before_arrays(void) { + write_fixture(1, 0, false, 0); reject_without_transition_allocation(); +} +static void test_maximum_type_and_index_are_valid(void) { + write_fixture(1, 256, false, 255); evaluate(EVENT_ROUTER_MATCH); +} +static void test_one_past_type_limit_rejected_before_arrays(void) { + write_fixture(1, 257, false, 0); reject_without_transition_allocation(); +} +static void test_out_of_range_transition_index_frees_both_arrays(void) { + write_fixture(1, 1, false, 1); evaluate(EVENT_ROUTER_ERROR); + TEST_ASSERT_EQUAL_UINT(2, transition_calls); TEST_ASSERT_EQUAL_INT(0, live_blocks); +} +static void test_extended_second_header_maximum_is_bounded(void) { + write_fixture(MAX_TRANSITIONS, 1, true, 0); evaluate(EVENT_ROUTER_MATCH); + TEST_ASSERT_EQUAL_UINT(2, transition_calls); + TEST_ASSERT_EQUAL_UINT(MAX_TRANSITIONS, allocation_counts[0]); +} +static void test_extended_second_header_one_past_rejected(void) { + write_fixture(MAX_TRANSITIONS + 1, 1, true, 0); reject_without_transition_allocation(); +} +static void test_truncated_data_block_rejected_before_arrays(void) { + TEST_ASSERT_EQUAL_INT(0, truncate(fixture, 54)); reject_without_transition_allocation(); +} +static void test_huge_leap_block_rejected_before_arrays(void) { + FILE *file = fopen(fixture, "wb"); TEST_ASSERT_NOT_NULL(file); + header(file, '\0', 1, 1, INT_MAX, INT_MAX); block(file, 1, 1, 4, 0); + TEST_ASSERT_EQUAL_INT(0, fclose(file)); reject_without_transition_allocation(); +} +static void test_existing_file_size_limit_accepts_maximum_buffer(void) { + /* A valid v1 data prefix padded to the parser's existing file-size limit. */ + TEST_ASSERT_EQUAL_INT(0, truncate(fixture, MAX_FILE_BYTES)); evaluate(EVENT_ROUTER_MATCH); + TEST_ASSERT_EQUAL_UINT(1, file_buffer_calls); +} +static void test_file_one_past_limit_rejected_before_buffer_allocation(void) { + TEST_ASSERT_EQUAL_INT(0, truncate(fixture, MAX_FILE_BYTES + 1)); + reject_without_transition_allocation(); TEST_ASSERT_EQUAL_UINT(0, file_buffer_calls); +} +static void test_first_array_allocation_failure_releases_partial_state(void) { + fail_transition_call = 1; evaluate(EVENT_ROUTER_ERROR); + TEST_ASSERT_EQUAL_INT(0, live_blocks); +} +static void test_second_array_allocation_failure_releases_partial_state(void) { + fail_transition_call = 2; evaluate(EVENT_ROUTER_ERROR); + TEST_ASSERT_EQUAL_INT(0, live_blocks); +} +static void test_invalid_timezone_recovery_after_definition_reload(void) { + write_fixture(MAX_TRANSITIONS + 1, 1, false, 0); reject_without_transition_allocation(); + event_route_delivery_plan_clear(&plan); + write_fixture(1, 1, false, 0); generation++; + evaluate(EVENT_ROUTER_MATCH); + TEST_ASSERT_EQUAL_UINT(2, transition_calls); +} +int main(void) { + UNITY_BEGIN(); + RUN_TEST(test_ordinary_delivery_allocates_two_bounded_arrays); + RUN_TEST(test_zero_transitions_needs_no_arrays); + RUN_TEST(test_maximum_transitions_retains_delivery); + RUN_TEST(test_one_past_transition_limit_rejected_before_arrays); + RUN_TEST(test_maximum_raw_count_rejected_before_arrays); + RUN_TEST(test_signed_boundary_count_rejected_before_arrays); + RUN_TEST(test_zero_types_rejected_before_arrays); + RUN_TEST(test_maximum_type_and_index_are_valid); + RUN_TEST(test_one_past_type_limit_rejected_before_arrays); + RUN_TEST(test_out_of_range_transition_index_frees_both_arrays); + RUN_TEST(test_extended_second_header_maximum_is_bounded); + RUN_TEST(test_extended_second_header_one_past_rejected); + RUN_TEST(test_truncated_data_block_rejected_before_arrays); + RUN_TEST(test_huge_leap_block_rejected_before_arrays); + RUN_TEST(test_existing_file_size_limit_accepts_maximum_buffer); + RUN_TEST(test_file_one_past_limit_rejected_before_buffer_allocation); + RUN_TEST(test_first_array_allocation_failure_releases_partial_state); + RUN_TEST(test_second_array_allocation_failure_releases_partial_state); + RUN_TEST(test_invalid_timezone_recovery_after_definition_reload); + return UNITY_END(); +} diff --git a/tests/unit/test_t7_2_buffers.c b/tests/unit/test_t7_2_buffers.c new file mode 100644 index 000000000..08a9ce88d --- /dev/null +++ b/tests/unit/test_t7_2_buffers.c @@ -0,0 +1,406 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include +#include +#include "unity.h" +#include "database/db_core.h" +#include "database/db_query_builder.h" +#include "database/db_zones.h" +#include "video/go2rtc/go2rtc_api.h" + +static sqlite3 *db; +static char *captured_url; +static unsigned requests; +static char scratch[8192], names[MAX_TRACKED_COLUMNS][64]; +static sqlite3_stmt *statement; +static bool fail_url_allocation; +static size_t rejected_allocation; +static unsigned allocation_rejections; + +void *__real_malloc(size_t); +void *__wrap_malloc(size_t size) { + /* The builder's aggregate URL is the only >=2048-byte malloc made by + * project code in this call. Delegate normal allocations unchanged. */ + if (fail_url_allocation && size >= 2048) { + rejected_allocation = size; + allocation_rejections++; + return NULL; + } + return __real_malloc(size); +} + +/* Only replace the connection provider; schema checks, query assembly and + * zone transactions/serialization are the actual production functions. */ +sqlite3 *__wrap_get_db_handle(void) { return db; } + +/* Real libcurl options and escaping, but no network or external camera. + * A successful synthetic upstream records exactly the URL passed to CURL. */ +CURLcode __real_curl_easy_getinfo(CURL *, CURLINFO, ...); +CURLcode __wrap_curl_easy_getinfo(CURL *curl, CURLINFO info, ...) { + va_list args; + va_start(args, info); + void *value = va_arg(args, void *); + va_end(args); + if (info == CURLINFO_RESPONSE_CODE) { + *(long *)value = 200; + return CURLE_OK; + } + return __real_curl_easy_getinfo(curl, info, value); +} +CURLcode __wrap_curl_easy_perform(CURL *curl) { + char *url = NULL; + CURLcode rc = __real_curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &url); + if (rc != CURLE_OK || !url) return CURLE_URL_MALFORMAT; + free(captured_url); + captured_url = strdup(url); + requests++; + return captured_url ? CURLE_OK : CURLE_OUT_OF_MEMORY; +} +static void sql(const char *query) { + TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, + sqlite3_exec(db, query, NULL, NULL, NULL), sqlite3_errmsg(db)); +} +void setUp(void) { + db = NULL; + statement = NULL; + captured_url = NULL; + requests = 0; + fail_url_allocation = false; + rejected_allocation = 0; + allocation_rejections = 0; + TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db)); + sql("CREATE TABLE t(id INTEGER); INSERT INTO t VALUES(1);"); + sql("CREATE TABLE detection_zones(id TEXT PRIMARY KEY, stream_name TEXT, " + "name TEXT, enabled INTEGER, color TEXT, polygon TEXT, filter_classes TEXT, " + "min_confidence REAL, created_at INTEGER, updated_at INTEGER);"); + TEST_ASSERT_TRUE(go2rtc_api_init("127.0.0.1", 1984)); +} +void tearDown(void) { + sqlite3_finalize(statement); + statement = NULL; + if (db) sqlite3_close(db); + db = NULL; + go2rtc_api_cleanup(); + free(captured_url); + captured_url = NULL; +} +static void builder(query_builder_t *qb) { + TEST_ASSERT_EQUAL_INT(0, qb_init(qb, "t")); + TEST_ASSERT_EQUAL_INT(0, qb_add_column(qb, "id", true)); +} +static void assert_executable(const char *query, int columns) { + TEST_ASSERT_NOT_NULL(query); + TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_prepare_v2(db, query, -1, &statement, NULL)); + TEST_ASSERT_EQUAL_INT(columns, sqlite3_column_count(statement)); + sqlite3_finalize(statement); + statement = NULL; +} +static void test_query_valid_where_order(void) { + query_builder_t qb; + builder(&qb); + const char *query = qb_build_select(&qb, "id=1", "id DESC"); + TEST_ASSERT_EQUAL_STRING("SELECT id FROM t WHERE id=1 ORDER BY id DESC;", query); + assert_executable(query, 1); + TEST_ASSERT_EQUAL_INT(0, qb_get_column_index(&qb, "id")); +} +static void query_where_boundary(size_t total_length, bool accepted) { + struct { unsigned before; query_builder_t qb; unsigned after; } guarded; + guarded.before = 0xaabbccdd; + guarded.after = 0xaabbccdd; + builder(&guarded.qb); + const size_t fixed = strlen("SELECT id FROM t WHERE ;"); + size_t clause_len = total_length - fixed; + memset(scratch, 'a', clause_len); + memcpy(scratch, "id=1 /*", 7); + memcpy(scratch + clause_len - 2, "*/", 2); + scratch[clause_len] = '\0'; + const char *query = qb_build_select(&guarded.qb, scratch, NULL); + TEST_ASSERT_EQUAL_HEX32(0xaabbccdd, guarded.before); + TEST_ASSERT_EQUAL_HEX32(0xaabbccdd, guarded.after); + if (accepted) { + TEST_ASSERT_NOT_NULL(query); + TEST_ASSERT_EQUAL_UINT(total_length, strlen(query)); + assert_executable(query, 1); + } else TEST_ASSERT_NULL(query); +} +static void test_query_exact_buffer_fit(void) { query_where_boundary(MAX_QUERY_LEN - 1, true); } +static void test_query_one_byte_overflow_rejected(void) { query_where_boundary(MAX_QUERY_LEN, false); } +static void test_query_oversized_where_and_order_rejected(void) { + query_builder_t qb; + builder(&qb); + memset(scratch, 'a', sizeof(scratch) - 1); + scratch[sizeof(scratch) - 1] = '\0'; + TEST_ASSERT_NULL(qb_build_select(&qb, scratch, NULL)); + TEST_ASSERT_NULL(qb_build_select(&qb, NULL, scratch)); +} +static void columns_table(query_builder_t *qb, int count) { + size_t used = (size_t)snprintf(scratch, sizeof(scratch), "CREATE TABLE b("); + for (int i = 0; i < count; i++) { + memset(names[i], 'a', 63); + names[i][0] = 'c'; + names[i][1] = (char)('0' + i / 10); + names[i][2] = (char)('0' + i % 10); + names[i][63] = '\0'; + int n = snprintf(scratch + used, sizeof(scratch) - used, + "%s%s INTEGER", i ? "," : "", names[i]); + TEST_ASSERT_TRUE(n > 0 && (size_t)n < sizeof(scratch) - used); + used += (size_t)n; + } + strcpy(scratch + used, ");"); + sql(scratch); + TEST_ASSERT_EQUAL_INT(0, qb_init(qb, "b")); + for (int i = 0; i < count; i++) + TEST_ASSERT_EQUAL_INT(0, qb_add_column(qb, names[i], true)); +} +static void test_query_long_column_list_that_fits(void) { + query_builder_t qb; + columns_table(&qb, 62); + assert_executable(qb_build_select(&qb, NULL, NULL), 62); +} +static void test_query_column_list_overflow_rejected(void) { + query_builder_t qb; + columns_table(&qb, MAX_TRACKED_COLUMNS); + TEST_ASSERT_NULL(qb_build_select(&qb, NULL, NULL)); +} + +static detection_zone_t zone(int count) { + detection_zone_t value = {0}; + strcpy(value.id, "zone"); + strcpy(value.name, "Original"); + strcpy(value.color, "#123456"); + value.enabled = true; + value.polygon_count = count; + for (int i = 0; i < MAX_ZONE_POINTS; i++) { + value.polygon[i].x = (float)(i % 2); + value.polygon[i].y = (float)((i / 2) % 2); + } + return value; +} +static void assert_original_zone(void) { + detection_zone_t result = {0}; + TEST_ASSERT_EQUAL_INT(1, get_detection_zones("camera", &result, 1)); + TEST_ASSERT_EQUAL_STRING("Original", result.name); + TEST_ASSERT_EQUAL_INT(3, result.polygon_count); +} +static void test_zone_maximum_normalized_points_round_trip(void) { + detection_zone_t input = zone(MAX_ZONE_POINTS), result = {0}; + TEST_ASSERT_EQUAL_INT(0, save_detection_zones("camera", &input, 1)); + TEST_ASSERT_EQUAL_INT(1, get_detection_zones("camera", &result, 1)); + TEST_ASSERT_EQUAL_INT(MAX_ZONE_POINTS, result.polygon_count); + for (int i = 0; i < MAX_ZONE_POINTS; i++) { + TEST_ASSERT_EQUAL_FLOAT(input.polygon[i].x, result.polygon[i].x); + TEST_ASSERT_EQUAL_FLOAT(input.polygon[i].y, result.polygon[i].y); + } +} +static void test_zone_serialized_overflow_rolls_back(void) { + detection_zone_t input = zone(3); + TEST_ASSERT_EQUAL_INT(0, save_detection_zones("camera", &input, 1)); + strcpy(input.name, "Replacement"); + input.polygon_count = MAX_ZONE_POINTS; + for (int i = 0; i < MAX_ZONE_POINTS; i++) + input.polygon[i].x = input.polygon[i].y = FLT_MAX; + TEST_ASSERT_EQUAL_INT(-1, save_detection_zones("camera", &input, 1)); + assert_original_zone(); +} +static void test_zone_zero_negative_counts_roll_back(void) { + detection_zone_t input = zone(3); + TEST_ASSERT_EQUAL_INT(0, save_detection_zones("camera", &input, 1)); + input.polygon_count = 0; + TEST_ASSERT_EQUAL_INT(-1, save_detection_zones("camera", &input, 1)); + assert_original_zone(); + input.polygon_count = -1; + TEST_ASSERT_EQUAL_INT(-1, save_detection_zones("camera", &input, 1)); + assert_original_zone(); +} +static void test_zone_excessive_point_count_rejected(void) { + detection_zone_t input = zone(MAX_ZONE_POINTS + 1); + TEST_ASSERT_EQUAL_INT(-1, save_detection_zones("camera", &input, 1)); +} +static void test_zone_one_and_two_points_are_not_polygons(void) { + detection_zone_t input = zone(1); + TEST_ASSERT_EQUAL_INT(-1, save_detection_zones("camera", &input, 1)); + input.polygon_count = 2; + TEST_ASSERT_EQUAL_INT(-1, save_detection_zones("camera", &input, 1)); +} +static void test_zone_invalid_later_batch_member_never_mutates_rows(void) { + detection_zone_t original = zone(3); + TEST_ASSERT_EQUAL_INT(0, save_detection_zones("camera", &original, 1)); + detection_zone_t batch[2] = {zone(3), zone(3)}; + strcpy(batch[0].id, "first-replacement"); + strcpy(batch[1].id, "invalid-replacement"); + const int invalid[] = {0, -1, 1, 2, MAX_ZONE_POINTS + 1, INT_MAX}; + sqlite3_int64 before = sqlite3_total_changes64(db); + for (size_t i = 0; i < sizeof(invalid) / sizeof(invalid[0]); i++) { + batch[1].polygon_count = invalid[i]; + TEST_ASSERT_EQUAL_INT(-1, save_detection_zones("camera", batch, 2)); + /* total_changes includes rolled-back writes, so this proves validation + * precedes DELETE/INSERT, not just eventual transaction rollback. */ + TEST_ASSERT_EQUAL_INT64(before, sqlite3_total_changes64(db)); + assert_original_zone(); + } +} +static void test_zone_empty_list_still_clears_zones(void) { + detection_zone_t input = zone(3), result; + TEST_ASSERT_EQUAL_INT(0, save_detection_zones("camera", &input, 1)); + TEST_ASSERT_EQUAL_INT(0, save_detection_zones("camera", &input, 0)); + TEST_ASSERT_EQUAL_INT(0, get_detection_zones("camera", &result, 1)); +} + +static void assert_query_identity(const char *stream, const char **sources, int count) { + TEST_ASSERT_NOT_NULL(captured_url); + const char *query = strchr(captured_url, '?'); + TEST_ASSERT_NOT_NULL(query); + int source_index = 0, stream_count = 0; + for (const char *p = query + 1; *p;) { + const char *end = strchr(p, '&'); + if (!end) end = p + strlen(p); + const char *equal = memchr(p, '=', (size_t)(end - p)); + TEST_ASSERT_NOT_NULL(equal); + int length = 0; + char *decoded = curl_easy_unescape(NULL, equal + 1, (int)(end - equal - 1), &length); + TEST_ASSERT_NOT_NULL(decoded); + bool same = false; + if (equal - p == 3 && strncmp(p, "src", 3) == 0) { + same = source_index < count && strlen(sources[source_index]) == (size_t)length + && memcmp(sources[source_index], decoded, (size_t)length) == 0; + if (!same && source_index < count) + fprintf(stderr, "source %d: expected %zu bytes, captured %d\n", + source_index, strlen(sources[source_index]), length); + source_index++; + } else if (equal - p == 4 && strncmp(p, "name", 4) == 0) { + same = strlen(stream) == (size_t)length && memcmp(stream, decoded, (size_t)length) == 0; + if (!same) fprintf(stderr, "stream name: expected %zu bytes, captured %d\n", + strlen(stream), length); + stream_count++; + } + curl_free(decoded); + TEST_ASSERT_TRUE_MESSAGE(same, "upstream received a changed source/name or unexpected parameter"); + p = *end ? end + 1 : end; + } + TEST_ASSERT_EQUAL_INT_MESSAGE(count, source_index, "upstream lost a source"); + TEST_ASSERT_EQUAL_INT(1, stream_count); +} +static void submit(const char *stream, const char **sources, int count, bool must_accept) { + bool ok = go2rtc_api_add_stream_multi(stream, sources, count); + if (must_accept) TEST_ASSERT_TRUE(ok); + if (!ok) { + TEST_ASSERT_EQUAL_UINT_MESSAGE(0, requests, "truncated request must not be sent"); + return; + } + TEST_ASSERT_EQUAL_UINT(1, requests); + assert_query_identity(stream, sources, count); +} +static void test_multi_source_valid_escaping_preserves_identity(void) { + const char *sources[] = {"rtsp://camera/a?token=x&name=y", "ffmpeg:camera#audio=opus"}; + submit("camera & east", sources, 2, true); +} +static void test_multi_source_maximum_valid_stream_name(void) { + char name[MAX_STREAM_NAME]; + memset(name, ' ', sizeof(name) - 1); name[sizeof(name) - 1] = '\0'; + const char *sources[] = {"rtsp://camera/a", "rtsp://camera/b"}; + submit(name, sources, 2, true); +} +static void test_multi_source_aggregate_overflow_never_sends_partial_sources(void) { + memset(scratch, '/', 1023); scratch[1023] = '\0'; + const char *sources[] = {"rtsp://camera/first", scratch}; + submit("camera", sources, 2, false); +} +static void test_multi_source_encoder_overflow_preserves_identity_or_rejects(void) { + memset(scratch, 'a', 3500); scratch[3500] = '\0'; + const char *sources[] = {scratch, "rtsp://camera/b", "rtsp://camera/c"}; + submit("camera", sources, 3, false); +} +static void test_multi_source_name_overflow_preserves_identity_or_rejects(void) { + char name[MAX_STREAM_NAME + 1]; + memset(name, ' ', sizeof(name) - 1); name[sizeof(name) - 1] = '\0'; + const char *sources[] = {"rtsp://camera/a", "rtsp://camera/b"}; + submit(name, sources, 2, false); +} +static void test_multi_source_exact_aggregate_buffer_fit(void) { + const char *sources[] = {scratch}; + /* All source bytes are unreserved, so encoded length equals raw length. */ + size_t fixed = strlen("http://127.0.0.1:1984") + strlen(go2rtc_api_get_base_path()) + + strlen("/api/streams?src=&name=camera"); + size_t source_len = 2048 - 1 - fixed; + memset(scratch, 'a', source_len); scratch[source_len] = '\0'; + submit("camera", sources, 1, true); + TEST_ASSERT_EQUAL_UINT(2047, strlen(captured_url)); +} +static void test_multi_source_one_byte_aggregate_overflow(void) { + const char *sources[] = {scratch}; + size_t fixed = strlen("http://127.0.0.1:1984") + strlen(go2rtc_api_get_base_path()) + + strlen("/api/streams?src=&name=camera"); + size_t source_len = 2048 - fixed; + memset(scratch, 'a', source_len); scratch[source_len] = '\0'; + submit("camera", sources, 1, false); +} +static void test_multi_source_more_than_four_valid_sources(void) { + const char *sources[] = {"rtsp://camera/1", "rtsp://camera/2", "rtsp://camera/3", + "rtsp://camera/4", "rtsp://camera/5"}; + submit("camera", sources, 5, true); +} +static void test_multi_source_nonpositive_counts_rejected(void) { + const char *sources[] = {"rtsp://camera/1"}; + TEST_ASSERT_FALSE(go2rtc_api_add_stream_multi("camera", sources, 0)); + TEST_ASSERT_FALSE(go2rtc_api_add_stream_multi("camera", sources, -1)); + TEST_ASSERT_EQUAL_UINT(0, requests); +} +static void test_multi_source_allocation_failure_sends_nothing(void) { + const char *sources[] = {"rtsp://camera/1", "rtsp://camera/2"}; + fail_url_allocation = true; + TEST_ASSERT_FALSE(go2rtc_api_add_stream_multi("camera", sources, 2)); + TEST_ASSERT_EQUAL_UINT(1, allocation_rejections); + TEST_ASSERT_EQUAL_UINT64(3072, rejected_allocation); + TEST_ASSERT_EQUAL_UINT(0, requests); +} +static void test_multi_source_maximum_count_arithmetic_before_allocation_failure(void) { + const char *sources[] = {"unused"}; + fail_url_allocation = true; + TEST_ASSERT_FALSE(go2rtc_api_add_stream_multi("camera", sources, INT_MAX)); + uint64_t expected = UINT64_C(1024) * ((uint64_t)INT_MAX + 1); + if (expected > SIZE_MAX) { + TEST_ASSERT_EQUAL_UINT(0, allocation_rejections); + } else { + TEST_ASSERT_EQUAL_UINT(1, allocation_rejections); + TEST_ASSERT_EQUAL_UINT64(expected, rejected_allocation); + } + TEST_ASSERT_EQUAL_UINT(0, requests); + /* Allocation is rejected before iteration; this does not claim that the + * one-entry fixture is an INT_MAX-length source array or support for it. */ +} +int main(void) { + if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK) return 2; + UNITY_BEGIN(); + RUN_TEST(test_query_valid_where_order); + RUN_TEST(test_query_exact_buffer_fit); + RUN_TEST(test_query_one_byte_overflow_rejected); + RUN_TEST(test_query_oversized_where_and_order_rejected); + RUN_TEST(test_query_long_column_list_that_fits); + RUN_TEST(test_query_column_list_overflow_rejected); + RUN_TEST(test_zone_maximum_normalized_points_round_trip); + RUN_TEST(test_zone_serialized_overflow_rolls_back); + RUN_TEST(test_zone_zero_negative_counts_roll_back); + RUN_TEST(test_zone_excessive_point_count_rejected); + RUN_TEST(test_zone_one_and_two_points_are_not_polygons); + RUN_TEST(test_zone_invalid_later_batch_member_never_mutates_rows); + RUN_TEST(test_zone_empty_list_still_clears_zones); + RUN_TEST(test_multi_source_valid_escaping_preserves_identity); + RUN_TEST(test_multi_source_maximum_valid_stream_name); + RUN_TEST(test_multi_source_aggregate_overflow_never_sends_partial_sources); + RUN_TEST(test_multi_source_encoder_overflow_preserves_identity_or_rejects); + RUN_TEST(test_multi_source_name_overflow_preserves_identity_or_rejects); + RUN_TEST(test_multi_source_exact_aggregate_buffer_fit); + RUN_TEST(test_multi_source_one_byte_aggregate_overflow); + RUN_TEST(test_multi_source_more_than_four_valid_sources); + RUN_TEST(test_multi_source_nonpositive_counts_rejected); + RUN_TEST(test_multi_source_allocation_failure_sends_nothing); + RUN_TEST(test_multi_source_maximum_count_arithmetic_before_allocation_failure); + int result = UNITY_END(); + curl_global_cleanup(); + return result; +} diff --git a/tests/unit/test_t7_2_files.c b/tests/unit/test_t7_2_files.c new file mode 100644 index 000000000..10d0906c4 --- /dev/null +++ b/tests/unit/test_t7_2_files.c @@ -0,0 +1,390 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "unity.h" +#include "core/config.h" +#include "core/daemon.h" +#include "core/path_utils.h" +#include "database/db_backup.h" +#include "database/db_core.h" +#include "storage/storage_manager.h" +#include "video/ffmpeg_utils.h" +#include "video/hls_writer.h" +#include "video/hls/hls_directory.h" +#include "video/hls/hls_context.h" + +static char root[128], watched[512], moved[512], replacement[512]; +static bool swap_on_chmod, swap_on_open, swap_on_stat, fail_chmod, hook_failed; +static int chmod_fd, owned_fd; +static mode_t saved_umask; +static config_t saved_config; +static hls_stream_ctx_t active; +static hls_stream_ctx_t *saved_context; +static hls_writer_t *writer; +static jpeg_encoder_cache_t *jpeg; +static bool block_worker_creation; +static int blocked_workers; + +sqlite3 *__wrap_get_db_handle(void) { return NULL; } +const config_t *__wrap_get_streaming_config(void) { return &g_config; } +/* Keep the public legacy retention entry point synchronous, with no catalog + * rows or background workers; its directory scan/stat/unlink path remains real. */ +int __real_pthread_create(pthread_t *, const pthread_attr_t *, void *(*)(void *), void *); +int __wrap_pthread_create(pthread_t *thread, const pthread_attr_t *attributes, + void *(*entry)(void *), void *arg) { + if (block_worker_creation) { blocked_workers++; errno = EAGAIN; return EAGAIN; } + return __real_pthread_create(thread, attributes, entry, arg); +} +int __wrap_storage_migration_worker_start(void) { return 0; } +int __wrap_storage_source_worker_start(void) { return 0; } +bool __wrap_storage_target_health_register(const char *a, const char *b, bool probe) { + (void)a; (void)b; (void)probe; return true; +} +int __wrap_get_all_stream_names_including_disabled(char names[][MAX_STREAM_NAME], int maximum) { + (void)names; (void)maximum; return 0; +} +int __wrap_get_orphaned_recording_stream_names(char names[][MAX_STREAM_NAME], int maximum) { + (void)names; (void)maximum; return 0; +} + +static int substitute_path(void) { + if (rename(watched, moved) != 0 || symlink(replacement, watched) != 0) { + hook_failed = true; + return -1; + } + return 0; +} +int __real_fchmod(int, mode_t); +int __wrap_fchmod(int fd, mode_t mode) { + chmod_fd = fd; + if (swap_on_chmod) { + swap_on_chmod = false; + if (substitute_path() != 0) return -1; + } + if (fail_chmod) { errno = EPERM; return -1; } + return __real_fchmod(fd, mode); +} +int __real_open(const char *, int, ...); +int __wrap_open(const char *path, int flags, ...) { + mode_t mode = 0; + if (flags & O_CREAT) { + va_list args; + va_start(args, flags); + mode = (mode_t)va_arg(args, int); + va_end(args); + } + int fd = __real_open(path, flags, mode); + if (fd >= 0 && swap_on_open && strcmp(path, watched) == 0) { + swap_on_open = false; + if (substitute_path() != 0) { close(fd); return -1; } + } + return fd; +} +int __real_fstatat(int, const char *, struct stat *, int); +int __wrap_fstatat(int fd, const char *name, struct stat *st, int flags) { + int result = __real_fstatat(fd, name, st, flags); + if (!result && swap_on_stat && strcmp(name, "old.ts") == 0) { + swap_on_stat = false; + if (rename(watched, moved) != 0 || mkdir(watched, 0700) != 0) hook_failed = true; + int replacement_fd = open(replacement, O_WRONLY | O_CREAT | O_EXCL, 0600); + if (replacement_fd < 0) hook_failed = true; + else { if (write(replacement_fd, "replacement", 11) != 11) hook_failed = true; close(replacement_fd); } + } + return result; +} +static void path(char *out, size_t size, const char *name) { + int length = snprintf(out, size, "%s/%s", root, name); + TEST_ASSERT_TRUE(length > 0 && (size_t)length < size); +} +static void make_file(const char *file, const char *bytes, mode_t mode) { + int fd = open(file, O_WRONLY | O_CREAT | O_TRUNC, mode); + TEST_ASSERT_GREATER_OR_EQUAL_INT(0, fd); + ssize_t count = write(fd, bytes, strlen(bytes)); + close(fd); + TEST_ASSERT_EQUAL_INT(strlen(bytes), count); +} +static mode_t permissions(const char *file) { + struct stat st; + TEST_ASSERT_EQUAL_INT(0, stat(file, &st)); + return st.st_mode & 0777; +} +static bool exists(const char *file) { struct stat st; return lstat(file, &st) == 0; } +static void remove_tree(const char *directory) { + DIR *dir = opendir(directory); + if (!dir) return; + struct dirent *entry; + while ((entry = readdir(dir))) { + if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue; + char file[1024]; + snprintf(file, sizeof(file), "%s/%s", directory, entry->d_name); + struct stat st; + if (!lstat(file, &st) && S_ISDIR(st.st_mode)) remove_tree(file); + else unlink(file); + } + closedir(dir); + rmdir(directory); +} +void setUp(void) { + strcpy(root, "/tmp/lightnvr_t7_files_XXXXXX"); + TEST_ASSERT_NOT_NULL(mkdtemp(root)); + saved_umask = umask(0); + saved_config = g_config; + saved_context = streaming_contexts[0]; + memset(&active, 0, sizeof(active)); + strcpy(active.config.name, "camera"); + active.running = true; + streaming_contexts[0] = &active; + g_config.max_streams = 1; + strcpy(g_config.storage_path, root); + g_config.storage_path_hls[0] = '\0'; + swap_on_chmod = swap_on_open = swap_on_stat = fail_chmod = hook_failed = false; + owned_fd = chmod_fd = -1; + writer = NULL; + jpeg = NULL; + block_worker_creation = false; + blocked_workers = 0; +} +void tearDown(void) { + swap_on_chmod = swap_on_open = swap_on_stat = fail_chmod = false; + if (owned_fd >= 0) close(owned_fd); + if (writer) hls_writer_close(writer); + if (jpeg) jpeg_encoder_cache_destroy(jpeg); + streaming_contexts[0] = saved_context; + g_config = saved_config; + umask(saved_umask); + remove_tree(root); +} +static void arm_substitution(void) { + path(watched, sizeof(watched), "owned"); + path(moved, sizeof(moved), "original-inode"); + path(replacement, sizeof(replacement), "outside-sentinel"); + make_file(watched, "original", 0600); + make_file(replacement, "untouched", 0600); +} +static void test_pid_permissions_follow_owned_descriptor_after_path_substitution(void) { + arm_substitution(); + swap_on_chmod = true; + owned_fd = write_pid_file(watched); + TEST_ASSERT_GREATER_OR_EQUAL_INT(0, owned_fd); + TEST_ASSERT_FALSE(hook_failed); + TEST_ASSERT_FALSE(swap_on_chmod); + TEST_ASSERT_EQUAL_INT(0644, permissions(moved)); + TEST_ASSERT_EQUAL_INT(0600, permissions(replacement)); + struct stat fd_info, original_info; + TEST_ASSERT_EQUAL_INT(0, fstat(owned_fd, &fd_info)); + TEST_ASSERT_EQUAL_INT(0, stat(moved, &original_info)); + TEST_ASSERT_EQUAL_UINT64(original_info.st_ino, fd_info.st_ino); +} +static void test_chmod_path_file_directory_and_symlink_controls(void) { + char file[512], directory[512], link[512]; + path(file, sizeof(file), "file"); path(directory, sizeof(directory), "directory"); + path(link, sizeof(link), "link"); + make_file(file, "payload", 0600); + TEST_ASSERT_EQUAL_INT(0, mkdir(directory, 0700)); + TEST_ASSERT_EQUAL_INT(0, chmod_path(file, 0640)); + TEST_ASSERT_EQUAL_INT(0, chmod_path(directory, 0750)); + TEST_ASSERT_EQUAL_INT(0640, permissions(file)); + TEST_ASSERT_EQUAL_INT(0750, permissions(directory)); + TEST_ASSERT_EQUAL_INT(0, symlink(file, link)); + TEST_ASSERT_EQUAL_INT(0, chmod_path(link, 0777)); + TEST_ASSERT_EQUAL_INT(0640, permissions(file)); +} +static void test_chmod_path_substitution_keeps_descriptor_identity(void) { + arm_substitution(); swap_on_chmod = true; + TEST_ASSERT_EQUAL_INT(0, chmod_path(watched, 0640)); + TEST_ASSERT_FALSE(hook_failed); + TEST_ASSERT_EQUAL_INT(0640, permissions(moved)); + TEST_ASSERT_EQUAL_INT(0600, permissions(replacement)); + TEST_ASSERT_EQUAL_INT(-1, fcntl(chmod_fd, F_GETFD)); + TEST_ASSERT_EQUAL_INT(EBADF, errno); +} +static void test_chmod_failure_releases_open_descriptor(void) { + arm_substitution(); fail_chmod = true; + TEST_ASSERT_EQUAL_INT(-1, chmod_path(watched, 0640)); + TEST_ASSERT_GREATER_OR_EQUAL_INT(0, chmod_fd); + TEST_ASSERT_EQUAL_INT(-1, fcntl(chmod_fd, F_GETFD)); + TEST_ASSERT_EQUAL_INT(EBADF, errno); +} +static void test_recursive_chmod_normal_tree(void) { + char directory[512], file[512]; + path(directory, sizeof(directory), "tree"); path(file, sizeof(file), "tree/file"); + TEST_ASSERT_EQUAL_INT(0, mkdir(directory, 0700)); + make_file(file, "payload", 0600); + TEST_ASSERT_EQUAL_INT(0, chmod_recursive(directory, 0750)); + TEST_ASSERT_EQUAL_INT(0750, permissions(directory)); + TEST_ASSERT_EQUAL_INT(0750, permissions(file)); +} +static void test_recursive_symlink_skip_preserves_unowned_descriptor(void) { + char sentinel[512], link[512]; + path(sentinel, sizeof(sentinel), "sentinel"); path(link, sizeof(link), "symlink"); + make_file(sentinel, "private", 0600); + TEST_ASSERT_EQUAL_INT(0, symlink(sentinel, link)); + fflush(NULL); + pid_t pid = fork(); + TEST_ASSERT_GREATER_OR_EQUAL_INT(0, pid); + if (pid == 0) { + /* This child's fd0 belongs exclusively to the disposable sentinel. + * Test-only zero initialization makes the missing fd_out assignment + * deterministic without affecting the parent's descriptors. */ + int fd = open(sentinel, O_RDONLY); + if (fd < 0 || dup2(fd, 0) < 0) _exit(20); + if (fd != 0) close(fd); + int status = chmod_recursive(link, 0777); + bool sentinel_open = fcntl(0, F_GETFD) >= 0; + if (!sentinel_open) _exit(21); + _exit(status == 0 ? 0 : 22); + } + int status = 0; + TEST_ASSERT_EQUAL_INT(pid, waitpid(pid, &status, 0)); + TEST_ASSERT_TRUE(WIFEXITED(status)); + TEST_ASSERT_EQUAL_INT_MESSAGE(0, WEXITSTATUS(status), "symlink skip consumed an unowned descriptor (21) or failed (22)"); + TEST_ASSERT_EQUAL_INT(0600, permissions(sentinel)); +} +static void create_database(const char *file, int value) { + sqlite3 *db = NULL; + TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(file, &db)); + char sql[128]; + snprintf(sql, sizeof(sql), "CREATE TABLE marker(value INTEGER); INSERT INTO marker VALUES(%d);", value); + int result = sqlite3_exec(db, sql, NULL, NULL, NULL); + sqlite3_close(db); + TEST_ASSERT_EQUAL_INT(SQLITE_OK, result); +} +static int database_value(const char *file) { + sqlite3 *db = NULL; sqlite3_stmt *stmt = NULL; + int value = -1; + if (sqlite3_open_v2(file, &db, SQLITE_OPEN_READONLY, NULL) == SQLITE_OK && + sqlite3_prepare_v2(db, "SELECT value FROM marker", -1, &stmt, NULL) == SQLITE_OK && + sqlite3_step(stmt) == SQLITE_ROW) value = sqlite3_column_int(stmt, 0); + sqlite3_finalize(stmt); sqlite3_close(db); + return value; +} +static void test_restore_reads_open_source_descriptor_after_path_substitution(void) { + char destination[512]; + path(watched, sizeof(watched), "backup.sqlite"); path(moved, sizeof(moved), "held.sqlite"); + path(replacement, sizeof(replacement), "replacement.sqlite"); path(destination, sizeof(destination), "destination.sqlite"); + create_database(watched, 11); create_database(replacement, 99); create_database(destination, 22); + swap_on_open = true; + TEST_ASSERT_EQUAL_INT(0, restore_database_from_backup(watched, destination)); + TEST_ASSERT_FALSE(hook_failed); TEST_ASSERT_FALSE(swap_on_open); + TEST_ASSERT_EQUAL_INT(11, database_value(destination)); + TEST_ASSERT_EQUAL_INT(99, database_value(replacement)); +} +static void test_missing_corrupt_restore_preserves_existing_database(void) { + char source[512], destination[512]; + path(source, sizeof(source), "backup.sqlite"); path(destination, sizeof(destination), "destination.sqlite"); + create_database(destination, 22); + TEST_ASSERT_EQUAL_INT(-1, restore_database_from_backup(source, destination)); + TEST_ASSERT_EQUAL_INT(22, database_value(destination)); + make_file(source, "not a sqlite database", 0600); + TEST_ASSERT_EQUAL_INT(-1, restore_database_from_backup(source, destination)); + TEST_ASSERT_EQUAL_INT(22, database_value(destination)); +} +static void test_delete_recording_unlinks_symlink_without_touching_target(void) { + char file[512], link[512]; + path(file, sizeof(file), "recording"); path(link, sizeof(link), "link.mp4"); + make_file(file, "payload", 0600); TEST_ASSERT_EQUAL_INT(0, symlink(file, link)); + TEST_ASSERT_EQUAL_INT(0, delete_recording(link)); + TEST_ASSERT_TRUE(exists(file)); TEST_ASSERT_FALSE(exists(link)); + TEST_ASSERT_EQUAL_INT(0, delete_recording(file)); + TEST_ASSERT_FALSE(exists(file)); + TEST_ASSERT_EQUAL_INT(-1, delete_recording(file)); +} +static void test_jpeg_new_files_have_restricted_creation_permissions(void) { + char direct[512], cached[512]; + unsigned char pixels[32 * 32 * 3] = {0}; + path(direct, sizeof(direct), "direct.jpg"); path(cached, sizeof(cached), "cached.jpg"); + TEST_ASSERT_EQUAL_INT(0, ffmpeg_encode_jpeg(pixels, 32, 32, 3, 80, direct)); + jpeg = jpeg_encoder_cache_create(32, 32, 3, 80); + TEST_ASSERT_NOT_NULL(jpeg); + TEST_ASSERT_EQUAL_INT(0, jpeg_encoder_cache_encode(jpeg, pixels, cached)); + TEST_ASSERT_EQUAL_INT(0640, permissions(direct)); + TEST_ASSERT_EQUAL_INT(0640, permissions(cached)); +} +static void test_hls_directory_and_writer_use_common_permission_helper(void) { + char directory[512]; + TEST_ASSERT_EQUAL_INT(0, ensure_hls_directory(directory, sizeof(directory), "camera")); + TEST_ASSERT_EQUAL_INT(0755, permissions(directory)); + writer = hls_writer_create(directory, "camera", 2); + TEST_ASSERT_NOT_NULL(writer); + TEST_ASSERT_EQUAL_STRING(directory, writer->output_dir); + TEST_ASSERT_EQUAL_INT(0755, permissions(writer->output_dir)); +} +static void old_file(const char *file) { + make_file(file, "segment", 0600); + struct timespec times[2] = {{time(NULL) - 600, 0}, {time(NULL) - 600, 0}}; + TEST_ASSERT_EQUAL_INT(0, utimensat(AT_FDCWD, file, times, 0)); +} +static void test_active_hls_removes_old_regular_ts_m4s_but_preserves_symlinks(void) { + char directory[512], ts[512], m4s[512], link[512], victim[512], recent[512]; + TEST_ASSERT_EQUAL_INT(0, ensure_hls_directory(directory, sizeof(directory), "camera")); + path(ts, sizeof(ts), "hls/camera/old.ts"); path(m4s, sizeof(m4s), "hls/camera/old.m4s"); + path(link, sizeof(link), "hls/camera/link.ts"); path(victim, sizeof(victim), "sentinel"); + path(recent, sizeof(recent), "hls/camera/recent.ts"); + old_file(ts); old_file(m4s); old_file(victim); make_file(recent, "recent", 0600); + TEST_ASSERT_EQUAL_INT(0, symlink(victim, link)); + cleanup_hls_directories(); + TEST_ASSERT_FALSE(exists(ts)); TEST_ASSERT_FALSE(exists(m4s)); + TEST_ASSERT_TRUE(exists(link)); TEST_ASSERT_TRUE(exists(victim)); TEST_ASSERT_TRUE(exists(recent)); +} +static void test_hls_unlinkat_stays_on_open_directory_after_parent_rename(void) { + char original_segment[512]; + TEST_ASSERT_EQUAL_INT(0, ensure_hls_directory(watched, sizeof(watched), "camera")); + path(moved, sizeof(moved), "moved-camera"); path(replacement, sizeof(replacement), "hls/camera/old.ts"); + path(original_segment, sizeof(original_segment), "moved-camera/old.ts"); + old_file(replacement); swap_on_stat = true; + cleanup_hls_directories(); + TEST_ASSERT_FALSE(hook_failed); TEST_ASSERT_FALSE(swap_on_stat); + TEST_ASSERT_FALSE(exists(original_segment)); TEST_ASSERT_TRUE(exists(replacement)); +} +static void test_legacy_retention_removes_old_regular_file_but_skips_symlink(void) { + char directory[512], old[512], link[512], target[512], recent[512]; + path(directory, sizeof(directory), "legacy"); + path(old, sizeof(old), "legacy/old.mp4"); path(link, sizeof(link), "legacy/link.mp4"); + path(target, sizeof(target), "outside-sentinel"); path(recent, sizeof(recent), "legacy/recent.mp4"); + TEST_ASSERT_EQUAL_INT(0, mkdir(directory, 0700)); + make_file(old, "old recording", 0600); make_file(target, "untouched", 0600); + make_file(recent, "recent recording", 0600); + struct timespec times[2] = {{time(NULL) - 172800, 0}, {time(NULL) - 172800, 0}}; + TEST_ASSERT_EQUAL_INT(0, utimensat(AT_FDCWD, old, times, 0)); + TEST_ASSERT_EQUAL_INT(0, utimensat(AT_FDCWD, target, times, 0)); + TEST_ASSERT_EQUAL_INT(0, symlink(target, link)); + block_worker_creation = true; + int initialized = init_storage_manager(root, 0); + block_worker_creation = false; + TEST_ASSERT_EQUAL_INT(0, initialized); + TEST_ASSERT_EQUAL_INT(1, blocked_workers); + TEST_ASSERT_EQUAL_INT(0, set_retention_days(1)); + TEST_ASSERT_EQUAL_INT(1, apply_retention_policy()); + TEST_ASSERT_FALSE(exists(old)); TEST_ASSERT_TRUE(exists(link)); + TEST_ASSERT_TRUE(exists(target)); TEST_ASSERT_TRUE(exists(recent)); + TEST_ASSERT_EQUAL_INT(0, set_retention_days(0)); +} +int main(void) { + UNITY_BEGIN(); + RUN_TEST(test_pid_permissions_follow_owned_descriptor_after_path_substitution); + RUN_TEST(test_chmod_path_file_directory_and_symlink_controls); + RUN_TEST(test_chmod_path_substitution_keeps_descriptor_identity); + RUN_TEST(test_chmod_failure_releases_open_descriptor); + RUN_TEST(test_recursive_chmod_normal_tree); + RUN_TEST(test_recursive_symlink_skip_preserves_unowned_descriptor); + RUN_TEST(test_restore_reads_open_source_descriptor_after_path_substitution); + RUN_TEST(test_missing_corrupt_restore_preserves_existing_database); + RUN_TEST(test_delete_recording_unlinks_symlink_without_touching_target); + RUN_TEST(test_jpeg_new_files_have_restricted_creation_permissions); + RUN_TEST(test_hls_directory_and_writer_use_common_permission_helper); + RUN_TEST(test_active_hls_removes_old_regular_ts_m4s_but_preserves_symlinks); + RUN_TEST(test_hls_unlinkat_stays_on_open_directory_after_parent_rename); + RUN_TEST(test_legacy_retention_removes_old_regular_file_but_skips_symlink); + return UNITY_END(); +} diff --git a/tests/unit/test_t7_2_migrations.c b/tests/unit/test_t7_2_migrations.c new file mode 100644 index 000000000..0fdd3e934 --- /dev/null +++ b/tests/unit/test_t7_2_migrations.c @@ -0,0 +1,158 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include "unity.h" +#include "database/sqlite_migrate.h" + +static char root[256], directory[320], path[384], outside[384]; +static sqlite3 *db; +static sqlite_migrate_t *migration; + +static const char valid_sql[] = + "-- migrate:up\nCREATE TABLE evidence(value TEXT);\n" + "INSERT INTO evidence VALUES('preserved');\n" + "-- migrate:down\nDROP TABLE evidence;\n"; + +static void write_fixture(const char *name, const char *sql) { + FILE *file = fopen(name, "w"); + TEST_ASSERT_NOT_NULL(file); + TEST_ASSERT_EQUAL(strlen(sql), fwrite(sql, 1, strlen(sql), file)); + TEST_ASSERT_EQUAL_INT(0, fclose(file)); + TEST_ASSERT_EQUAL_INT(0, chmod(name, 0600)); +} + +static int scalar(const char *sql) { + sqlite3_stmt *statement = NULL; + TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_prepare_v2(db, sql, -1, &statement, NULL)); + TEST_ASSERT_EQUAL_INT(SQLITE_ROW, sqlite3_step(statement)); + int value = sqlite3_column_int(statement, 0); + TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_finalize(statement)); + return value; +} + +static void open_migrations(void) { + migrate_config_t config = { .migrations_dir = directory }; + migration = migrate_init(db, &config); + TEST_ASSERT_NOT_NULL(migration); +} + +void setUp(void) { + strcpy(root, "/tmp/lightnvr-migration-boundary-XXXXXX"); + TEST_ASSERT_NOT_NULL(mkdtemp(root)); + snprintf(directory, sizeof(directory), "%s/migrations", root); + snprintf(path, sizeof(path), "%s/0001_evidence.sql", directory); + snprintf(outside, sizeof(outside), "%s/migrations-outside.sql", root); + TEST_ASSERT_EQUAL_INT(0, mkdir(directory, 0700)); + TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db)); + migration = NULL; +} + +void tearDown(void) { + migrate_free(migration); + sqlite3_close(db); + unlink(path); + unlink(outside); + rmdir(directory); + rmdir(root); +} + +static void assert_rejected_without_schema_effect(void) { + migrate_stats_t stats; + TEST_ASSERT_EQUAL_INT(-1, migrate_up(migration, &stats)); + TEST_ASSERT_EQUAL_INT(1, stats.failed); + TEST_ASSERT_EQUAL_INT(0, scalar("SELECT count(*) FROM sqlite_master WHERE name='evidence'")); + TEST_ASSERT_EQUAL_INT(0, scalar("SELECT count(*) FROM schema_migrations")); +} + +void test_regular_private_migration_applies_and_rolls_back(void) { + write_fixture(path, valid_sql); + open_migrations(); + migrate_stats_t stats; + TEST_ASSERT_EQUAL_INT(0, migrate_up(migration, &stats)); + TEST_ASSERT_EQUAL_INT(1, stats.applied); + TEST_ASSERT_EQUAL_INT(1, scalar("SELECT count(*) FROM evidence WHERE value='preserved'")); + TEST_ASSERT_EQUAL_INT(1, migrate_down(migration)); + TEST_ASSERT_EQUAL_INT(0, scalar("SELECT count(*) FROM sqlite_master WHERE name='evidence'")); + TEST_ASSERT_EQUAL_INT(0, scalar("SELECT count(*) FROM schema_migrations")); +} + +void test_outside_symlink_is_not_executed(void) { + write_fixture(outside, valid_sql); + TEST_ASSERT_EQUAL_INT(0, symlink(outside, path)); + open_migrations(); + migrate_stats_t stats; + int result = migrate_up(migration, &stats); + /* readdir may identify a symlink immediately or report DT_UNKNOWN. */ + if (stats.total == 0) TEST_ASSERT_EQUAL_INT(0, result); + else { TEST_ASSERT_EQUAL_INT(-1, result); TEST_ASSERT_EQUAL_INT(1, stats.failed); } + TEST_ASSERT_EQUAL_INT(0, scalar("SELECT count(*) FROM sqlite_master WHERE name='evidence'")); + TEST_ASSERT_EQUAL_INT(0, scalar("SELECT count(*) FROM schema_migrations")); + TEST_ASSERT_EQUAL_INT(0, access(outside, F_OK)); +} + +void test_world_writable_migration_is_not_executed(void) { + write_fixture(path, valid_sql); + TEST_ASSERT_EQUAL_INT(0, chmod(path, 0666)); + open_migrations(); + assert_rejected_without_schema_effect(); +} + +void test_file_replaced_after_discovery_is_rechecked_before_execution(void) { + write_fixture(path, valid_sql); + write_fixture(outside, valid_sql); + open_migrations(); + TEST_ASSERT_EQUAL_INT(0, unlink(path)); + char traversal[512]; + snprintf(traversal, sizeof(traversal), "%s/../migrations-outside.sql", directory); + TEST_ASSERT_EQUAL_INT(0, symlink(traversal, path)); + assert_rejected_without_schema_effect(); +} + +void test_disallowed_attach_rejects_entire_migration_before_effects(void) { + char sql[1024]; + snprintf(sql, sizeof(sql), "-- migrate:up\nCREATE TABLE evidence(value TEXT);\n" + "ATTACH DATABASE '%s' AS outside_db;\n-- migrate:down\nDROP TABLE evidence;\n", outside); + write_fixture(path, sql); + open_migrations(); + assert_rejected_without_schema_effect(); + TEST_ASSERT_EQUAL_INT(-1, access(outside, F_OK)); + TEST_ASSERT_EQUAL_INT(1, scalar("SELECT count(*) FROM pragma_database_list")); +} + +void test_rollback_rechecks_replaced_file_and_preserves_applied_state(void) { + write_fixture(path, valid_sql); + open_migrations(); + TEST_ASSERT_EQUAL_INT(0, migrate_up(migration, NULL)); + write_fixture(outside, valid_sql); + TEST_ASSERT_EQUAL_INT(0, unlink(path)); + TEST_ASSERT_EQUAL_INT(0, symlink(outside, path)); + TEST_ASSERT_EQUAL_INT(-1, migrate_down(migration)); + TEST_ASSERT_EQUAL_INT(1, scalar("SELECT count(*) FROM evidence WHERE value='preserved'")); + TEST_ASSERT_EQUAL_INT(1, scalar("SELECT count(*) FROM schema_migrations")); +} + +void test_rollback_rechecks_world_writable_mode(void) { + write_fixture(path, valid_sql); + open_migrations(); + TEST_ASSERT_EQUAL_INT(0, migrate_up(migration, NULL)); + TEST_ASSERT_EQUAL_INT(0, chmod(path, 0666)); + TEST_ASSERT_EQUAL_INT(-1, migrate_down(migration)); + TEST_ASSERT_EQUAL_INT(1, scalar("SELECT count(*) FROM evidence")); + TEST_ASSERT_EQUAL_INT(1, scalar("SELECT count(*) FROM schema_migrations")); +} + +int main(void) { + UNITY_BEGIN(); + RUN_TEST(test_regular_private_migration_applies_and_rolls_back); + RUN_TEST(test_outside_symlink_is_not_executed); + RUN_TEST(test_world_writable_migration_is_not_executed); + RUN_TEST(test_file_replaced_after_discovery_is_rechecked_before_execution); + RUN_TEST(test_disallowed_attach_rejects_entire_migration_before_effects); + RUN_TEST(test_rollback_rechecks_replaced_file_and_preserves_applied_state); + RUN_TEST(test_rollback_rechecks_world_writable_mode); + return UNITY_END(); +} diff --git a/tests/unit/test_t7_2_settings.c b/tests/unit/test_t7_2_settings.c new file mode 100644 index 000000000..62a8a3d7a --- /dev/null +++ b/tests/unit/test_t7_2_settings.c @@ -0,0 +1,226 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include "unity.h" +#include "core/config.h" +#include "core/logger.h" +#include "database/db_core.h" +#include "web/api_handlers.h" +#include "web/api_handlers_system.h" +#include "web/request_response.h" +#include "telemetry/collectors/linux_filesystem.h" + +static char root[256], database[MAX_PATH_LENGTH], literal[MAX_PATH_LENGTH]; +static config_t baseline, persisted; +static unsigned saves, shell_calls; +static http_response_t response; + +static int remove_owned_backups(void) { + char directory[MAX_PATH_LENGTH + 16]; + snprintf(directory, sizeof(directory), "%s.backups", database); + DIR *dir = opendir(directory); + if (!dir) return errno == ENOENT ? 0 : -1; + int result = 0; + struct dirent *entry; + while ((entry = readdir(dir))) { + if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue; + /* unlinkat removes only entries in our private backup directory; + * it never follows a link or recursively removes an unknown tree. */ + if (unlinkat(dirfd(dir), entry->d_name, 0) != 0) result = -1; + } + if (closedir(dir) != 0) result = -1; + if (rmdir(directory) != 0) result = -1; + return result; +} + +/* Capture the handler's persistence boundary; never write a host config file. */ +int __wrap_save_config(const config_t *config, const char *path) { + (void)path; + persisted = *config; + saves++; + return 0; +} + +int __wrap_system(const char *command) { + (void)command; + shell_calls++; + errno = EACCES; + return -1; +} + +FILE *__wrap_popen(const char *command, const char *mode) { + (void)command; (void)mode; + shell_calls++; + errno = EACCES; + return NULL; +} + +void setUp(void) { + g_config = baseline; + saves = shell_calls = 0; + memset(&persisted, 0, sizeof(persisted)); + http_response_init(&response); +} + +void tearDown(void) { + http_response_free(&response); +} + +static void post_path(const char *field, const char *value) { + cJSON *body = cJSON_CreateObject(); + TEST_ASSERT_NOT_NULL(body); + cJSON_AddStringToObject(body, field, value); + char *json = cJSON_PrintUnformatted(body); + cJSON_Delete(body); + TEST_ASSERT_NOT_NULL(json); + http_request_t request; + http_request_init(&request); + request.body = json; + request.body_len = strlen(json); + handle_post_settings(&request, &response); + free(json); +} + +static void assert_rejected(const char *field, const char *value) { + g_config = baseline; + http_response_free(&response); + http_response_init(&response); + post_path(field, value); + TEST_ASSERT_EQUAL_INT_MESSAGE(400, response.status_code, value); + TEST_ASSERT_EQUAL_UINT(0, saves); + TEST_ASSERT_EQUAL_UINT(0, shell_calls); + TEST_ASSERT_EQUAL_STRING(baseline.storage_path, g_config.storage_path); + TEST_ASSERT_EQUAL_STRING(baseline.storage_path_hls, g_config.storage_path_hls); + TEST_ASSERT_EQUAL_STRING(baseline.db_post_backup_script, g_config.db_post_backup_script); +} + +void test_absolute_storage_path_with_spaces_is_persisted_without_shell(void) { + char accepted[MAX_PATH_LENGTH]; + snprintf(accepted, sizeof(accepted), "%s/recording space", root); + post_path("storage_path", accepted); + TEST_ASSERT_EQUAL_INT(200, response.status_code); + TEST_ASSERT_EQUAL_UINT(1, saves); + TEST_ASSERT_EQUAL_STRING(accepted, persisted.storage_path); + TEST_ASSERT_EQUAL_UINT(0, shell_calls); +} + +void test_optional_hls_override_can_be_set_and_cleared(void) { + post_path("storage_path_hls", root); + TEST_ASSERT_EQUAL_INT(200, response.status_code); + TEST_ASSERT_EQUAL_STRING(root, persisted.storage_path_hls); + http_response_free(&response); + http_response_init(&response); + post_path("storage_path_hls", ""); + TEST_ASSERT_EQUAL_INT(200, response.status_code); + TEST_ASSERT_EQUAL_UINT(2, saves); + TEST_ASSERT_EQUAL_STRING("", persisted.storage_path_hls); + TEST_ASSERT_EQUAL_UINT(0, shell_calls); +} + +void test_backup_hook_path_is_saved_without_executing_it(void) { + char accepted[MAX_PATH_LENGTH]; + snprintf(accepted, sizeof(accepted), "%s/nonexistent-hook", root); + post_path("db_post_backup_script", accepted); + TEST_ASSERT_EQUAL_INT(200, response.status_code); + TEST_ASSERT_EQUAL_UINT(1, saves); + TEST_ASSERT_EQUAL_STRING(accepted, persisted.db_post_backup_script); + TEST_ASSERT_EQUAL_UINT(0, shell_calls); +} + +void test_relative_and_empty_primary_storage_paths_are_rejected(void) { + assert_rejected("storage_path", "relative/path"); + assert_rejected("storage_path", ""); + assert_rejected("storage_path_hls", "relative/path"); + assert_rejected("db_post_backup_script", "relative/hook"); +} + +void test_shell_metacharacters_cannot_reach_path_persistence(void) { + const char *fields[] = { "storage_path", "storage_path_hls", "db_post_backup_script" }; + const char characters[] = ";|&`$><\n\r\\\"'!{}()[]~*?"; + for (size_t f = 0; f < sizeof(fields) / sizeof(fields[0]); f++) { + for (size_t i = 0; i < strlen(characters); i++) { + char candidate[MAX_PATH_LENGTH]; + snprintf(candidate, sizeof(candidate), "%s/path%cmarker", root, characters[i]); + assert_rejected(fields[f], candidate); + } + } +} + +void test_cached_system_response_uses_no_shell(void) { + /* This endpoint reads cached filesystem telemetry and DB recording sizes; + * it does not itself sample this configured path. The next test exercises + * the actual filesystem collector separately. */ + strcpy(g_config.storage_path, literal); + http_request_t request; + http_request_init(&request); + handle_get_system_info(&request, &response); + TEST_ASSERT_EQUAL_INT(200, response.status_code); + TEST_ASSERT_EQUAL_UINT(0, shell_calls); + TEST_ASSERT_EQUAL_UINT(0, saves); + TEST_ASSERT_EQUAL_INT(0, access(literal, F_OK)); + cJSON *json = cJSON_Parse(response.body); + TEST_ASSERT_NOT_NULL(json); + TEST_ASSERT_TRUE(cJSON_IsObject(json)); + cJSON_Delete(json); +} + +void test_filesystem_collector_samples_literal_path_without_shell(void) { + linux_filesystem_resource_t resource = {0}; + strcpy(resource.logical_id, "recordings"); + strcpy(resource.path, literal); + linux_filesystem_sample_t sample; + TEST_ASSERT_EQUAL_INT(0, linux_filesystem_sample(&resource, "/proc/self/mountinfo", &sample)); + TEST_ASSERT_EQUAL_INT(SYSTEM_HEALTH_CAPABILITY_AVAILABLE, sample.capacity_bytes.capability); + struct statvfs expected; + TEST_ASSERT_EQUAL_INT(0, statvfs(literal, &expected)); + TEST_ASSERT_EQUAL_UINT64((uint64_t)expected.f_blocks * expected.f_frsize, sample.capacity_bytes.value); + TEST_ASSERT_EQUAL_STRING("recordings", sample.logical_id); + TEST_ASSERT_EQUAL_UINT(0, shell_calls); + TEST_ASSERT_EQUAL_INT(0, access(literal, F_OK)); +} + +int main(void) { + strcpy(root, "/tmp/lightnvr-settings-boundary-XXXXXX"); + if (!mkdtemp(root)) return 1; + snprintf(database, sizeof(database), "%s/test.db", root); + snprintf(literal, sizeof(literal), "%s/literal;echo not-a-command", root); + if (mkdir(literal, 0700) != 0) return 1; + init_logger(); + load_default_config(&g_config); + g_config.web_auth_enabled = false; + g_config.go2rtc_enabled = false; + g_config.mqtt_enabled = false; + strcpy(g_config.storage_path, root); + strcpy(g_config.db_path, database); + if (init_database(database) != 0) return 1; + baseline = g_config; + UNITY_BEGIN(); + RUN_TEST(test_absolute_storage_path_with_spaces_is_persisted_without_shell); + RUN_TEST(test_optional_hls_override_can_be_set_and_cleared); + RUN_TEST(test_backup_hook_path_is_saved_without_executing_it); + RUN_TEST(test_relative_and_empty_primary_storage_paths_are_rejected); + RUN_TEST(test_shell_metacharacters_cannot_reach_path_persistence); + RUN_TEST(test_cached_system_response_uses_no_shell); + RUN_TEST(test_filesystem_collector_samples_literal_path_without_shell); + int result = UNITY_END(); + shutdown_database(); + const char *suffixes[] = { "", "-wal", "-shm", ".bak" }; + for (size_t i = 0; i < sizeof(suffixes) / sizeof(suffixes[0]); i++) { + char file[MAX_PATH_LENGTH + 8]; + snprintf(file, sizeof(file), "%s%s", database, suffixes[i]); + unlink(file); + } + free(g_config.streams); + g_config.streams = NULL; + if (remove_owned_backups() != 0) result = 1; + if (rmdir(literal) != 0) result = 1; + if (rmdir(root) != 0) result = 1; + shutdown_logger(); + return result; +} diff --git a/tests/unit/test_t7_53.c b/tests/unit/test_t7_53.c new file mode 100644 index 000000000..954926228 --- /dev/null +++ b/tests/unit/test_t7_53.c @@ -0,0 +1,164 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include "unity.h" +/* Include the actual implementation for the arithmetic-only static helper seam. + * Its public definitions satisfy archive references; no replacement algorithm. */ +#include "../../src/core/authorization.c" + +static unsigned resize_calls, load_calls, filter_loads, filter_frees; +static bool fail_resize, deny_grants, fail_filter; +static authorization_context_t *context; +static user_t user; +static fleet_camera_t camera; +void *__real_realloc(void *, size_t); +void *__wrap_realloc(void *memory, size_t bytes) { + resize_calls++; + if (fail_resize || bytes > 1024 * 1024) return NULL; + return __real_realloc(memory, bytes); +} +int __wrap_db_authorization_load_user_grants(int64_t user_id, const char *action, + authorization_grant_t **grants, int *count, int64_t *version) { + (void)action; load_calls++; *version = 17; *count = deny_grants ? 0 : 1; + *grants = NULL; + if (!*count) return 0; + *grants = calloc(1, sizeof(**grants)); + if (!*grants) return -1; + strcpy((*grants)->scope_type, "collection"); + snprintf((*grants)->collection_uuid, sizeof((*grants)->collection_uuid), + "00000000-0000-4000-8000-%012lld", (long long)user_id); + strcpy((*grants)->uuid, "11111111-1111-4111-8111-111111111111"); + strcpy((*grants)->role_name, "fixture"); + return 0; +} +camera_collection_filter_result_t __wrap_camera_collection_filter_load_for_authorization( + const char *uuid, camera_collection_filter_t *filter) { + (void)uuid; filter_loads++; memset(filter, 0, sizeof(*filter)); + if (fail_filter) return CAMERA_COLLECTION_FILTER_DATABASE_ERROR; + filter->active = true; + return CAMERA_COLLECTION_FILTER_OK; +} +bool __wrap_camera_collection_filter_matches(const camera_collection_filter_t *filter, + const fleet_camera_t *candidate) { return filter->active && candidate != NULL; } +void __wrap_camera_collection_filter_free(camera_collection_filter_t *filter) { + filter_frees++; memset(filter, 0, sizeof(*filter)); +} +void setUp(void) { + resize_calls = load_calls = filter_loads = filter_frees = 0; + fail_resize = deny_grants = fail_filter = false; + context = authorization_context_create(); TEST_ASSERT_NOT_NULL(context); + memset(&user, 0, sizeof(user)); user.id = 1; user.is_active = true; + strcpy(user.authorization_mode, "policy"); + memset(&camera, 0, sizeof(camera)); +} +void tearDown(void) { + authorization_context_free(context); context = NULL; + TEST_ASSERT_EQUAL_UINT(filter_loads, filter_frees); +} +static void allowed(void) { + authorization_evaluation_t evaluation; + TEST_ASSERT_EQUAL_INT(0, authorization_evaluate_in_context(context, &user, + AUTHZ_LIVE_VIEW, &camera, &evaluation)); + TEST_ASSERT_EQUAL_INT(AUTHZ_DECISION_ALLOW, evaluation.decision); + TEST_ASSERT_EQUAL_INT(AUTHZ_SOURCE_POLICY_GRANT, evaluation.source); + TEST_ASSERT_EQUAL_INT64(17, evaluation.policy_version); +} +static void test_zero_context_and_repeated_permission_hit(void) { + TEST_ASSERT_EQUAL_INT(0, context->grant_count); + TEST_ASSERT_EQUAL_INT(0, context->collection_count); + allowed(); allowed(); + TEST_ASSERT_EQUAL_UINT(1, load_calls); TEST_ASSERT_EQUAL_UINT(1, filter_loads); + TEST_ASSERT_EQUAL_UINT(2, resize_calls); +} +static void test_both_caches_cross_first_capacity_without_permission_change(void) { + for (int i = 1; i <= 5; i++) { user.id = i; allowed(); } + TEST_ASSERT_EQUAL_INT(5, context->grant_count); + TEST_ASSERT_EQUAL_INT(5, context->collection_count); + TEST_ASSERT_EQUAL_INT(8, context->grant_capacity); + TEST_ASSERT_EQUAL_INT(8, context->collection_capacity); + TEST_ASSERT_EQUAL_UINT(4, resize_calls); + for (int i = 1; i <= 5; i++) { user.id = i; allowed(); } + TEST_ASSERT_EQUAL_UINT(5, load_calls); TEST_ASSERT_EQUAL_UINT(5, filter_loads); +} +static void test_grant_resize_failure_preserves_prior_permissions(void) { + for (int i = 1; i <= 4; i++) { user.id = i; allowed(); } + void *before = context->grants; fail_resize = true; user.id = 5; + authorization_evaluation_t evaluation; + TEST_ASSERT_EQUAL_INT(-1, authorization_evaluate_in_context(context, &user, + AUTHZ_LIVE_VIEW, &camera, &evaluation)); + TEST_ASSERT_EQUAL_INT(AUTHZ_DECISION_DENY, evaluation.decision); + TEST_ASSERT_EQUAL_PTR(before, context->grants); + TEST_ASSERT_EQUAL_INT(4, context->grant_count); + TEST_ASSERT_EQUAL_INT(4, context->grant_capacity); + user.id = 1; allowed(); + fail_resize = false; user.id = 5; allowed(); +} +static void test_collection_resize_failure_preserves_prior_filters(void) { + for (int i = 1; i <= 4; i++) { user.id = i; allowed(); } + /* Populate grant entry independently so failure specifically reaches the + * collection resize, not the preceding grant resize. */ + TEST_ASSERT_NOT_NULL(context_grants(context, 5, AUTHZ_LIVE_VIEW, "live.view")); + void *before = context->collections; fail_resize = true; user.id = 5; + authorization_evaluation_t evaluation; + TEST_ASSERT_EQUAL_INT(-1, authorization_evaluate_in_context(context, &user, + AUTHZ_LIVE_VIEW, &camera, &evaluation)); + TEST_ASSERT_EQUAL_INT(AUTHZ_DECISION_DENY, evaluation.decision); + TEST_ASSERT_EQUAL_PTR(before, context->collections); + TEST_ASSERT_EQUAL_INT(4, context->collection_count); + TEST_ASSERT_EQUAL_INT(4, context->collection_capacity); + user.id = 1; allowed(); fail_resize = false; user.id = 5; allowed(); +} +static void test_no_grants_default_denies_without_collection_allocation(void) { + deny_grants = true; authorization_evaluation_t evaluation; + TEST_ASSERT_EQUAL_INT(0, authorization_evaluate_in_context(context, &user, + AUTHZ_LIVE_VIEW, &camera, &evaluation)); + TEST_ASSERT_EQUAL_INT(AUTHZ_DECISION_DENY, evaluation.decision); + TEST_ASSERT_EQUAL_UINT(0, filter_loads); +} +static void test_failed_collection_stays_failed_and_cleans_up(void) { + fail_filter = true; authorization_evaluation_t evaluation; + for (int i = 0; i < 2; i++) { + TEST_ASSERT_EQUAL_INT(-1, authorization_evaluate_in_context(context, &user, + AUTHZ_LIVE_VIEW, &camera, &evaluation)); + TEST_ASSERT_EQUAL_INT(AUTHZ_DECISION_DENY, evaluation.decision); + } + TEST_ASSERT_EQUAL_UINT(1, filter_loads); +} +static void test_growth_zero_and_small_capacity(void) { + int capacity = 0; + void *memory = grow_array(NULL, &capacity, 1); TEST_ASSERT_NOT_NULL(memory); + TEST_ASSERT_EQUAL_INT(4, capacity); + memory = grow_array(memory, &capacity, 1); TEST_ASSERT_NOT_NULL(memory); + TEST_ASSERT_EQUAL_INT(8, capacity); free(memory); +} +static void test_largest_nonoverflowing_double_reaches_allocator_failure_unchanged(void) { + int capacity = INT_MAX / 2; fail_resize = true; + TEST_ASSERT_NULL(grow_array(NULL, &capacity, 1)); + TEST_ASSERT_EQUAL_INT(INT_MAX / 2, capacity); TEST_ASSERT_EQUAL_UINT(1, resize_calls); +} +static void test_signed_doubling_overflow_rejected_before_allocator(void) { + int capacity = INT_MAX / 2 + 1; + TEST_ASSERT_NULL(grow_array(NULL, &capacity, 1)); + TEST_ASSERT_EQUAL_INT(INT_MAX / 2 + 1, capacity); TEST_ASSERT_EQUAL_UINT(0, resize_calls); +} +static void test_size_multiplication_overflow_rejected_before_allocator(void) { + int capacity = 4; + void *result = grow_array(NULL, &capacity, SIZE_MAX / 8 + 1); + bool rejected = result == NULL; + free(result); + TEST_ASSERT_TRUE(rejected); + TEST_ASSERT_EQUAL_INT(4, capacity); TEST_ASSERT_EQUAL_UINT(0, resize_calls); +} +int main(void) { + UNITY_BEGIN(); + RUN_TEST(test_zero_context_and_repeated_permission_hit); + RUN_TEST(test_both_caches_cross_first_capacity_without_permission_change); + RUN_TEST(test_grant_resize_failure_preserves_prior_permissions); + RUN_TEST(test_collection_resize_failure_preserves_prior_filters); + RUN_TEST(test_no_grants_default_denies_without_collection_allocation); + RUN_TEST(test_failed_collection_stays_failed_and_cleans_up); + RUN_TEST(test_growth_zero_and_small_capacity); + RUN_TEST(test_largest_nonoverflowing_double_reaches_allocator_failure_unchanged); + RUN_TEST(test_signed_doubling_overflow_rejected_before_allocator); + RUN_TEST(test_size_multiplication_overflow_rejected_before_allocator); + return UNITY_END(); +} diff --git a/tests/unit/test_t7_54.c b/tests/unit/test_t7_54.c new file mode 100644 index 000000000..74ac72464 --- /dev/null +++ b/tests/unit/test_t7_54.c @@ -0,0 +1,183 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include +#include "unity.h" +#include "core/logger.h" +#include "core/logger_json.h" +#define READ_LIMIT (100U * 1024U) +static char root[128], path[256]; +static char **logs; +static int count, initialized; +static bool monitor, bad_tell, oversized_return; +static size_t short_limit, requested_read, first_allocation; +static unsigned allocations, reads, dup_calls, fail_allocation, fail_dup; +static const char entry[] = "{\"timestamp\":\"2026-01-01T00:00:00\",\"level\":\"info\",\"message\":\"tail\"}"; +void *__real_malloc(size_t); +void *__wrap_malloc(size_t bytes) { + if (monitor) { + allocations++; + if (allocations == 1) first_allocation = bytes; + if (allocations == fail_allocation) return NULL; + } + return __real_malloc(bytes); +} +char *__real_strdup(const char *); +char *__wrap_strdup(const char *text) { + if (monitor && ++dup_calls == fail_dup) return NULL; + return __real_strdup(text); +} +long __real_ftell(FILE *); +long __wrap_ftell(FILE *file) { return monitor && bad_tell ? -1 : __real_ftell(file); } +size_t __real_fread(void *, size_t, size_t, FILE *); +size_t __wrap_fread(void *buffer, size_t width, size_t number, FILE *file) { + if (!monitor) return __real_fread(buffer, width, number, file); + reads++; requested_read = number; + size_t result = __real_fread(buffer, width, number < short_limit ? number : short_limit, file); + /* Impossible libc return-value seam, without writing outside requested bytes. */ + return oversized_return ? SIZE_MAX : result; +} +void setUp(void) { + strcpy(root, "/tmp/lightnvr-t7-54-XXXXXX"); TEST_ASSERT_NOT_NULL(mkdtemp(root)); + snprintf(path, sizeof(path), "%s/log.json", root); + logs = NULL; count = 0; initialized = 0; + monitor = bad_tell = oversized_return = false; + short_limit = SIZE_MAX; requested_read = first_allocation = 0; + allocations = reads = dup_calls = fail_allocation = fail_dup = 0; + TEST_ASSERT_EQUAL_INT(0, init_json_logger(path)); initialized = 1; + TEST_ASSERT_EQUAL_INT(0, truncate(path, 0)); + log_clear_thread_context(); +} +void tearDown(void) { + monitor = false; + for (int i = 0; i < count; i++) free(logs[i]); + free(logs); logs = NULL; count = 0; + log_clear_thread_context(); + if (initialized) shutdown_json_logger(); + unlink(path); TEST_ASSERT_EQUAL_INT(0, rmdir(root)); +} +static void raw(const char *text) { + FILE *file = fopen(path, "w"); TEST_ASSERT_NOT_NULL(file); + TEST_ASSERT_EQUAL_UINT(strlen(text), fwrite(text, 1, strlen(text), file)); + TEST_ASSERT_EQUAL_INT(0, fclose(file)); +} +static void retrieve(int expected, int expected_count) { + monitor = true; + int result = get_json_logs("debug", "", &logs, &count); + monitor = false; + TEST_ASSERT_EQUAL_INT(expected, result); TEST_ASSERT_EQUAL_INT(expected_count, count); + if (expected != 0) TEST_ASSERT_NULL(logs); + TEST_ASSERT_LESS_OR_EQUAL_UINT(READ_LIMIT, requested_read); +} +static void tail(void) { + TEST_ASSERT_EQUAL_INT(1, count); + cJSON *object = cJSON_Parse(logs[0]); TEST_ASSERT_NOT_NULL(object); + const cJSON *message = cJSON_GetObjectItemCaseSensitive(object, "message"); + bool valid = cJSON_IsString(message) && strcmp(message->valuestring, "tail") == 0; + cJSON_Delete(object); TEST_ASSERT_TRUE(valid); +} +static void test_empty_file_returns_no_allocation(void) { + retrieve(0, 0); TEST_ASSERT_NULL(logs); TEST_ASSERT_EQUAL_UINT(0, allocations); +} +static void test_one_byte_invalid_json_is_terminated_and_ignored(void) { + raw("x"); retrieve(0, 0); TEST_ASSERT_EQUAL_UINT(2, first_allocation); +} +static void test_final_line_without_newline_is_retained(void) { + raw(entry); retrieve(0, 1); tail(); +} +static void test_blank_and_malformed_lines_do_not_change_valid_record(void) { + char text[512]; snprintf(text, sizeof(text), "\ninvalid\n{}\n%s\n\n", entry); + raw(text); retrieve(0, 1); tail(); +} +static void test_json_escape_and_thread_context_round_trip(void) { + const char *message = "quote\" slash\\ newline\n tab\t control\001 utf8\xc3\xa9"; + log_set_thread_context("component\"", "camera\nname"); + TEST_ASSERT_EQUAL_INT(0, write_json_log(LOG_LEVEL_WARN, "2026-01-01", message)); + retrieve(0, 1); + cJSON *object = cJSON_Parse(logs[0]); TEST_ASSERT_NOT_NULL(object); + bool valid = strcmp(cJSON_GetObjectItem(object, "message")->valuestring, message) == 0 && + strcmp(cJSON_GetObjectItem(object, "component")->valuestring, "component\"") == 0 && + strcmp(cJSON_GetObjectItem(object, "stream")->valuestring, "camera\nname") == 0 && + strcmp(cJSON_GetObjectItem(object, "level")->valuestring, "warning") == 0; + cJSON_Delete(object); TEST_ASSERT_TRUE(valid); + TEST_ASSERT_NULL(strchr(logs[0], '\n')); +} +static void test_upstream_formatter_keeps_existing_4095_byte_truncation(void) { + char message[5001]; memset(message, 'a', sizeof(message)-1); message[5000] = '\0'; + log_message(LOG_LEVEL_INFO, "%s", message); + retrieve(0, 1); + cJSON *object = cJSON_Parse(logs[0]); TEST_ASSERT_NOT_NULL(object); + cJSON *value = cJSON_GetObjectItem(object, "message"); + bool valid = cJSON_IsString(value) && strlen(value->valuestring) == 4095 && + strspn(value->valuestring, "a") == 4095; + cJSON_Delete(object); TEST_ASSERT_TRUE(valid); +} +static void sized_file(size_t length) { + FILE *file = fopen(path, "w"); TEST_ASSERT_NOT_NULL(file); + size_t padding = length - strlen(entry) - 1; + for (size_t i = 0; i < padding; i++) fputc(' ', file); + fputs(entry, file); fputc('\n', file); TEST_ASSERT_EQUAL_INT(0, fclose(file)); + retrieve(0, 1); tail(); + size_t bounded = length < READ_LIMIT ? length : READ_LIMIT; + TEST_ASSERT_EQUAL_UINT(bounded, requested_read); + TEST_ASSERT_EQUAL_UINT(bounded + 1, first_allocation); +} +static void test_one_below_read_limit(void) { sized_file(READ_LIMIT - 1); } +static void test_exact_read_limit(void) { sized_file(READ_LIMIT); } +static void test_one_past_read_limit(void) { sized_file(READ_LIMIT + 1); } +static void test_tail_window_skips_partial_leading_record(void) { + FILE *file = fopen(path, "w"); TEST_ASSERT_NOT_NULL(file); + for (unsigned i = 0; i < READ_LIMIT; i++) fputc('x', file); + fprintf(file, "\n%s\n", entry); TEST_ASSERT_EQUAL_INT(0, fclose(file)); + retrieve(0, 1); tail(); TEST_ASSERT_EQUAL_UINT(READ_LIMIT + 1, first_allocation); +} +static void test_short_read_terminates_at_actual_read(void) { + char text[512]; snprintf(text, sizeof(text), "%s\n%s\n", entry, entry); raw(text); + short_limit = strlen(entry) + 1; retrieve(0, 1); tail(); +} +static void test_zero_read_returns_no_records(void) { + raw(entry); short_limit = 0; retrieve(0, 0); TEST_ASSERT_NULL(logs); +} +static void test_failed_tell_rejects_before_allocation(void) { + raw(entry); bad_tell = true; retrieve(-1, 0); + TEST_ASSERT_EQUAL_UINT(0, allocations); TEST_ASSERT_EQUAL_UINT(0, reads); +} +static void test_impossible_oversized_read_return_is_clamped(void) { + raw(entry); oversized_return = true; retrieve(0, 1); tail(); +} +static void test_read_buffer_allocation_failure_clears_outputs(void) { + raw(entry); fail_allocation = 1; retrieve(-1, 0); TEST_ASSERT_EQUAL_UINT(0, reads); +} +static void test_lines_allocation_failure_releases_buffer(void) { + raw(entry); fail_allocation = 2; retrieve(-1, 0); +} +static void test_second_line_copy_failure_releases_previous_line(void) { + char text[512]; snprintf(text, sizeof(text), "%s\n%s\n", entry, entry); raw(text); + fail_dup = 2; retrieve(-1, 0); TEST_ASSERT_EQUAL_UINT(2, dup_calls); +} +int main(void) { + init_logger(); + UNITY_BEGIN(); + RUN_TEST(test_empty_file_returns_no_allocation); + RUN_TEST(test_one_byte_invalid_json_is_terminated_and_ignored); + RUN_TEST(test_final_line_without_newline_is_retained); + RUN_TEST(test_blank_and_malformed_lines_do_not_change_valid_record); + RUN_TEST(test_json_escape_and_thread_context_round_trip); + RUN_TEST(test_upstream_formatter_keeps_existing_4095_byte_truncation); + RUN_TEST(test_one_below_read_limit); + RUN_TEST(test_exact_read_limit); + RUN_TEST(test_one_past_read_limit); + RUN_TEST(test_tail_window_skips_partial_leading_record); + RUN_TEST(test_short_read_terminates_at_actual_read); + RUN_TEST(test_zero_read_returns_no_records); + RUN_TEST(test_failed_tell_rejects_before_allocation); + RUN_TEST(test_impossible_oversized_read_return_is_clamped); + RUN_TEST(test_read_buffer_allocation_failure_clears_outputs); + RUN_TEST(test_lines_allocation_failure_releases_buffer); + RUN_TEST(test_second_line_copy_failure_releases_previous_line); + int result = UNITY_END(); shutdown_logger(); return result; +} diff --git a/tests/unit/test_t7_55.c b/tests/unit/test_t7_55.c new file mode 100644 index 000000000..9efaf0782 --- /dev/null +++ b/tests/unit/test_t7_55.c @@ -0,0 +1,143 @@ +/* Exercise the actual private name classifier, never the server entry point or + * PID-file/signal path. All /proc fopen requests are intercepted below. */ +#define main t7_55_unexecuted_server_main +#include "../../src/core/main.c" +#undef main +#include "unity.h" + +static FILE *fixture; +static bool armed, fail_open, wrong_stream, missing_terminator; +static unsigned opens, reads, closes; +static int requested_size; +static char requested_path[96]; + +FILE *__real_fopen(const char *, const char *); +char *__real_fgets(char *, int, FILE *); +int __real_fclose(FILE *); + +FILE *__wrap_fopen(const char *path, const char *mode) { + if (!armed) return __real_fopen(path, mode); + opens++; + snprintf(requested_path, sizeof(requested_path), "%s", path); + if (strcmp(mode, "r") != 0) wrong_stream = true; + if (fail_open) { errno = EACCES; return NULL; } + return fixture; +} + +char *__wrap_fgets(char *buffer, int size, FILE *file) { + if (!armed) return __real_fgets(buffer, size, file); + reads++; + requested_size = size; + if (file != fixture) wrong_stream = true; + char *result = __real_fgets(buffer, size, file); + if (result && (size <= 0 || !memchr(buffer, '\0', (size_t)size))) + missing_terminator = true; + return result; +} + +int __wrap_fclose(FILE *file) { + if (armed) { + closes++; + if (file != fixture) wrong_stream = true; + if (file == fixture) fixture = NULL; + } + return __real_fclose(file); +} + +void setUp(void) { + fixture = NULL; + armed = fail_open = wrong_stream = missing_terminator = false; + opens = reads = closes = 0; + requested_size = 0; + requested_path[0] = '\0'; +} + +void tearDown(void) { + armed = false; + if (fixture) { TEST_ASSERT_EQUAL_INT(0, fclose(fixture)); fixture = NULL; } +} + +static void classify(const void *bytes, size_t length, bool expected) { + fixture = tmpfile(); + TEST_ASSERT_NOT_NULL(fixture); + TEST_ASSERT_EQUAL_size_t(length, fwrite(bytes, 1, length, fixture)); + rewind(fixture); + armed = true; + bool result = is_lightnvr_process(INT_MAX); + armed = false; + TEST_ASSERT_EQUAL(expected, result); + TEST_ASSERT_EQUAL_UINT(1, opens); + TEST_ASSERT_EQUAL_UINT(1, reads); + TEST_ASSERT_EQUAL_UINT(1, closes); + TEST_ASSERT_EQUAL_INT(32, requested_size); + TEST_ASSERT_EQUAL_STRING("/proc/2147483647/comm", requested_path); + TEST_ASSERT_FALSE(wrong_stream); + TEST_ASSERT_FALSE(missing_terminator); + TEST_ASSERT_NULL(fixture); +} + +static void test_exact_name_with_newline_matches(void) { classify("lightnvr\n", 9, true); } +static void test_exact_name_without_newline_matches(void) { classify("lightnvr", 8, true); } +static void test_unrelated_name_is_rejected(void) { classify("go2rtc\n", 7, false); } +static void test_name_prefix_is_not_an_exact_match(void) { classify("lightnvr-worker\n", 16, false); } +static void test_newline_only_is_rejected(void) { classify("\n", 1, false); } +static void test_empty_read_retains_existing_conservative_result(void) { classify("", 0, true); } +static void test_newline_stops_before_following_bytes(void) { classify("lightnvr\nother", 14, true); } +static void test_embedded_nul_stays_within_fgets_buffer(void) { + static const char bytes[] = {'l','i','g','h','t','n','v','r','\0','x','\n'}; + classify(bytes, sizeof(bytes), true); +} +static void test_full_buffer_without_newline_is_terminated(void) { + char bytes[31]; memset(bytes, 'x', sizeof(bytes)); + classify(bytes, sizeof(bytes), false); +} +static void test_overlong_input_is_bounded_and_rejected(void) { + char bytes[4096]; memset(bytes, 'x', sizeof(bytes)); + classify(bytes, sizeof(bytes), false); +} +static void test_open_failure_preserves_conservative_result(void) { + fail_open = armed = true; + bool result = is_lightnvr_process(123); + armed = false; + TEST_ASSERT_TRUE(result); + TEST_ASSERT_EQUAL_UINT(1, opens); + TEST_ASSERT_EQUAL_UINT(0, reads); + TEST_ASSERT_EQUAL_UINT(0, closes); + TEST_ASSERT_EQUAL_STRING("/proc/123/comm", requested_path); +} +static ssize_t read_error(void *cookie, char *buffer, size_t size) { + (void)cookie; (void)buffer; (void)size; + errno = EIO; + return -1; +} +static void test_read_error_closes_stream_and_preserves_conservative_result(void) { + cookie_io_functions_t io = {.read = read_error}; + fixture = fopencookie(NULL, "r", io); + TEST_ASSERT_NOT_NULL(fixture); + armed = true; + bool result = is_lightnvr_process(123); + armed = false; + TEST_ASSERT_TRUE(result); + TEST_ASSERT_EQUAL_UINT(1, opens); + TEST_ASSERT_EQUAL_UINT(1, reads); + TEST_ASSERT_EQUAL_UINT(1, closes); + TEST_ASSERT_FALSE(wrong_stream); + TEST_ASSERT_NULL(fixture); +} + +int main(void) { + UNITY_BEGIN(); + RUN_TEST(test_exact_name_with_newline_matches); + RUN_TEST(test_exact_name_without_newline_matches); + RUN_TEST(test_unrelated_name_is_rejected); + RUN_TEST(test_name_prefix_is_not_an_exact_match); + RUN_TEST(test_newline_only_is_rejected); + RUN_TEST(test_empty_read_retains_existing_conservative_result); + RUN_TEST(test_newline_stops_before_following_bytes); + RUN_TEST(test_embedded_nul_stays_within_fgets_buffer); + RUN_TEST(test_full_buffer_without_newline_is_terminated); + RUN_TEST(test_overlong_input_is_bounded_and_rejected); + RUN_TEST(test_open_failure_preserves_conservative_result); + RUN_TEST(test_read_error_closes_stream_and_preserves_conservative_result); + return UNITY_END(); +} diff --git a/tests/unit/test_t7_58.c b/tests/unit/test_t7_58.c new file mode 100644 index 000000000..8e228c64a --- /dev/null +++ b/tests/unit/test_t7_58.c @@ -0,0 +1,218 @@ +#define _POSIX_C_SOURCE 200809L +/* Exercise the actual private helpers, preserving their production visibility. */ +#include "../../src/telemetry/providers/linux_hardware.c" +#include "unity.h" + +#include +#include +#include + +static char root[128], path[192]; +static char *output; +static bool tracking; +static FILE *owned_file; +static unsigned opened, closed; + +FILE *__real_fopen(const char *, const char *); +FILE *__wrap_fopen(const char *name, const char *mode) { + FILE *file = __real_fopen(name, mode); + if (tracking && file) { owned_file = file; opened++; } + return file; +} +int __real_fclose(FILE *file); +int __wrap_fclose(FILE *file) { + if (file == owned_file) { owned_file = NULL; closed++; } + return __real_fclose(file); +} + +static void write_input(const void *bytes, size_t length) { + FILE *file = fopen(path, "wb"); + TEST_ASSERT_NOT_NULL(file); + size_t written = fwrite(bytes, 1, length, file); + int result = fclose(file); + TEST_ASSERT_EQUAL_size_t(length, written); + TEST_ASSERT_EQUAL_INT(0, result); +} +static bool read_input(const char *name, system_health_capability_t *capability) { + memset(output, 0xa5, HARDWARE_TEXT_MAX); + tracking = true; + bool result = read_text(name, output, capability); + tracking = false; + TEST_ASSERT_NULL(owned_file); + TEST_ASSERT_EQUAL_UINT(opened, closed); + return result; +} +void setUp(void) { + strcpy(root, "/tmp/lightnvr-t7-58-XXXXXX"); + TEST_ASSERT_NOT_NULL(mkdtemp(root)); + snprintf(path, sizeof(path), "%s/attribute", root); + output = malloc(HARDWARE_TEXT_MAX); /* Exact-sized allocation: ASan redzones. */ + TEST_ASSERT_NOT_NULL(output); + owned_file = NULL; opened = closed = 0; tracking = false; +} +void tearDown(void) { + tracking = false; + bool leaked = owned_file != NULL; + if (owned_file) { __real_fclose(owned_file); owned_file = NULL; } + free(output); output = NULL; + int removed = unlink(path); + bool file_cleanup = removed == 0 || errno == ENOENT; + int directory_cleanup = rmdir(root); + TEST_ASSERT_FALSE(leaked); + TEST_ASSERT_TRUE(file_cleanup); + TEST_ASSERT_EQUAL_INT(0, directory_cleanup); +} + +static void test_empty_and_every_all_whitespace_length_are_bounded_errors(void) { + char bytes[HARDWARE_TEXT_MAX]; memset(bytes, ' ', sizeof(bytes)); + for (size_t n = 0; n < HARDWARE_TEXT_MAX; ++n) { + write_input(bytes, n); + system_health_capability_t capability; + TEST_ASSERT_FALSE(read_input(path, &capability)); + TEST_ASSERT_EQUAL(SYSTEM_HEALTH_CAPABILITY_ERROR, capability); + TEST_ASSERT_EQUAL_CHAR('\0', output[0]); + } +} +static void test_every_leading_whitespace_length_trims_to_last_payload(void) { + char bytes[HARDWARE_TEXT_MAX]; + for (size_t n = 1; n < HARDWARE_TEXT_MAX; ++n) { + memset(bytes, ' ', n); bytes[n - 1] = '7'; + write_input(bytes, n); + system_health_capability_t capability; + TEST_ASSERT_TRUE(read_input(path, &capability)); + TEST_ASSERT_EQUAL(SYSTEM_HEALTH_CAPABILITY_AVAILABLE, capability); + TEST_ASSERT_EQUAL_STRING("7", output); + } +} +static void test_every_trailing_whitespace_length_preserves_first_payload(void) { + char bytes[HARDWARE_TEXT_MAX]; + for (size_t n = 1; n < HARDWARE_TEXT_MAX; ++n) { + memset(bytes, '\t', n); bytes[0] = '8'; + write_input(bytes, n); + system_health_capability_t capability; + TEST_ASSERT_TRUE(read_input(path, &capability)); + TEST_ASSERT_EQUAL_STRING("8", output); + } +} +static void test_exact_capacity_minus_one_payload_is_terminated(void) { + char bytes[HARDWARE_TEXT_MAX - 1]; memset(bytes, '9', sizeof(bytes)); + write_input(bytes, sizeof(bytes)); + system_health_capability_t capability; + TEST_ASSERT_TRUE(read_input(path, &capability)); + TEST_ASSERT_EQUAL_MEMORY(bytes, output, sizeof(bytes)); + TEST_ASSERT_EQUAL_CHAR('\0', output[sizeof(bytes)]); +} +static void test_overlong_and_one_past_capacity_inputs_are_rejected(void) { + char bytes[HARDWARE_TEXT_MAX + 1]; memset(bytes, '9', sizeof(bytes)); + for (size_t n = HARDWARE_TEXT_MAX; n <= sizeof(bytes); ++n) { + write_input(bytes, n); + system_health_capability_t capability; + TEST_ASSERT_FALSE(read_input(path, &capability)); + TEST_ASSERT_EQUAL(SYSTEM_HEALTH_CAPABILITY_ERROR, capability); + /* False callers discard output: no terminator is promised on failure. */ + } +} +static void test_all_unsigned_character_values_use_valid_ctype_indices(void) { + for (unsigned byte = 1; byte <= UCHAR_MAX; ++byte) { + unsigned char value = (unsigned char)byte; + write_input(&value, 1); + system_health_capability_t capability; + bool whitespace = byte == ' ' || byte == '\t' || byte == '\n' || + byte == '\r' || byte == '\v' || byte == '\f'; + TEST_ASSERT_EQUAL_INT(!whitespace, read_input(path, &capability)); + TEST_ASSERT_EQUAL_CHAR(whitespace ? 0 : value, output[0]); + if (!whitespace) TEST_ASSERT_EQUAL_CHAR(0, output[1]); + } +} +static void test_missing_and_actual_read_error_close_owned_stream(void) { + system_health_capability_t capability; + TEST_ASSERT_FALSE(read_input(path, &capability)); + TEST_ASSERT_EQUAL(SYSTEM_HEALTH_CAPABILITY_UNSUPPORTED, capability); + TEST_ASSERT_EQUAL_UINT(0, opened); + /* Linux permits fopen(directory); fread then reports EISDIR. */ + TEST_ASSERT_FALSE(read_input(root, &capability)); + TEST_ASSERT_EQUAL(SYSTEM_HEALTH_CAPABILITY_ERROR, capability); + TEST_ASSERT_EQUAL_UINT(1, opened); + TEST_ASSERT_EQUAL_UINT(1, closed); +} +static void test_integer_caller_consumes_trimmed_value_and_rejects_overflow(void) { + const char valid[] = " \t18446744073709551615\r\n"; + write_input(valid, sizeof(valid) - 1); + hardware_integer_t reading = read_integer(path, 10); + TEST_ASSERT_TRUE(reading.valid); + TEST_ASSERT_EQUAL_UINT64(UINT64_MAX, reading.value); + const char invalid[] = "18446744073709551616"; + write_input(invalid, sizeof(invalid) - 1); + reading = read_integer(path, 10); + TEST_ASSERT_FALSE(reading.valid); + TEST_ASSERT_EQUAL(SYSTEM_HEALTH_CAPABILITY_ERROR, reading.capability); +} +static void test_literal_separator_and_empty_parts_match_known_hash(void) { + char *empty = malloc(1); + TEST_ASSERT_NOT_NULL(empty); empty[0] = 0; + uint64_t actual = scoped_hash(empty, empty, empty, 0); + free(empty); + TEST_ASSERT_EQUAL_HEX64(UINT64_C(0xbc28bea9bf3e71a7), actual); + TEST_ASSERT_EQUAL_HEX64(UINT64_C(0x7b58946416c9a930), + scoped_hash("installation-A", "fan", "hwmon0", 1)); + TEST_ASSERT_EQUAL_HEX64(UINT64_C(0x75386f57550426fb), + scoped_hash("installation-B", "fan", "hwmon0", 1)); +} +static void test_hash_exact_size_longest_scope_and_channel_inputs(void) { + char *scope = malloc(LINUX_HARDWARE_INSTALLATION_SCOPE_LENGTH); + char *name = malloc(sizeof("hwmon999")); + TEST_ASSERT_NOT_NULL(scope); TEST_ASSERT_NOT_NULL(name); + memset(scope, 'A', LINUX_HARDWARE_INSTALLATION_SCOPE_LENGTH - 1); + scope[LINUX_HARDWARE_INSTALLATION_SCOPE_LENGTH - 1] = 0; + memcpy(name, "hwmon999", sizeof("hwmon999")); + uint64_t actual = scoped_hash(scope, "fan", name, 999); + free(scope); free(name); + TEST_ASSERT_EQUAL_HEX64(UINT64_C(0x84594dec82b8f0f8), actual); + TEST_ASSERT_EQUAL_UINT32(UINT32_MAX, UINT_MAX); + TEST_ASSERT_EQUAL_HEX64(UINT64_C(0xfa4fb3faf2c57446), + scoped_hash("installation-A", "fan", "hwmon0", UINT_MAX)); +} +static void test_scope_initialization_bounds_terminated_hash_input(void) { + char source[192]; memset(source, 'A', sizeof(source)); source[191] = 0; + linux_hardware_state_t state; + linux_hardware_state_init(&state, source); + TEST_ASSERT_EQUAL_size_t(95, strlen(state.installation_scope)); + TEST_ASSERT_EQUAL_CHAR(0, state.installation_scope[95]); + TEST_ASSERT_EQUAL_HEX64(UINT64_C(0x84594dec82b8f0f8), + scoped_hash(state.installation_scope, "fan", "hwmon999", 999)); +} + +static void test_public_entry_points_reject_null_required_inputs(void) { + linux_hardware_state_t state; + linux_hardware_state_init(&state, "fixture"); + system_health_collect_context_t context = {.sys_root = root}; + system_health_provider_inventory_t inventory; + system_health_observation_sink_t sink = {0}; + TEST_ASSERT_EQUAL_INT(-1, linux_hardware_discover(NULL, &context, &inventory)); + TEST_ASSERT_EQUAL_INT(-1, linux_hardware_discover(&state, NULL, &inventory)); + TEST_ASSERT_EQUAL_INT(-1, linux_hardware_discover(&state, &context, NULL)); + TEST_ASSERT_EQUAL_INT(-1, linux_hardware_collect(NULL, &context, &sink)); + TEST_ASSERT_EQUAL_INT(-1, linux_hardware_collect(&state, NULL, &sink)); + TEST_ASSERT_EQUAL_INT(-1, linux_hardware_collect(&state, &context, NULL)); + context.sys_root = NULL; + TEST_ASSERT_EQUAL_INT(-1, linux_hardware_discover(&state, &context, &inventory)); + TEST_ASSERT_EQUAL_INT(-1, linux_hardware_collect(&state, &context, &sink)); +} + +int main(void) { + if (!setlocale(LC_CTYPE, "C")) return 1; + UNITY_BEGIN(); + RUN_TEST(test_empty_and_every_all_whitespace_length_are_bounded_errors); + RUN_TEST(test_every_leading_whitespace_length_trims_to_last_payload); + RUN_TEST(test_every_trailing_whitespace_length_preserves_first_payload); + RUN_TEST(test_exact_capacity_minus_one_payload_is_terminated); + RUN_TEST(test_overlong_and_one_past_capacity_inputs_are_rejected); + RUN_TEST(test_all_unsigned_character_values_use_valid_ctype_indices); + RUN_TEST(test_missing_and_actual_read_error_close_owned_stream); + RUN_TEST(test_integer_caller_consumes_trimmed_value_and_rejects_overflow); + RUN_TEST(test_literal_separator_and_empty_parts_match_known_hash); + RUN_TEST(test_hash_exact_size_longest_scope_and_channel_inputs); + RUN_TEST(test_scope_initialization_bounds_terminated_hash_input); + RUN_TEST(test_public_entry_points_reject_null_required_inputs); + return UNITY_END(); +} diff --git a/tests/unit/test_t7_59.c b/tests/unit/test_t7_59.c new file mode 100644 index 000000000..a355c8ad3 --- /dev/null +++ b/tests/unit/test_t7_59.c @@ -0,0 +1,231 @@ +#define _POSIX_C_SOURCE 200809L +/* Trace actual constructed workers; never manufacture an invalid private tier. */ +#include "../../src/telemetry/system_health.c" +#include "unity.h" +#include + +static system_health_runtime_t *observed_runtime; +static atomic_uint wait_mask, bad_worker, calls[SYSTEM_HEALTH_TIER_COUNT]; +static unsigned created, joined; +static pthread_t owned_threads[SYSTEM_HEALTH_TIER_COUNT]; +static bool observe; +static _Thread_local struct timespec last_monotonic; +static _Thread_local bool have_monotonic; + +int __real_clock_gettime(clockid_t clock, struct timespec *value); +int __wrap_clock_gettime(clockid_t clock, struct timespec *value) { + int result = __real_clock_gettime(clock, value); + if (result == 0 && clock == CLOCK_MONOTONIC) { + last_monotonic = *value; have_monotonic = true; + } + return result; +} +int __real_pthread_create(pthread_t *, const pthread_attr_t *, void *(*)(void *), void *); +int __wrap_pthread_create(pthread_t *thread, const pthread_attr_t *attr, + void *(*entry)(void *), void *argument) { + bool tracked = observe && entry == tier_worker_main; + if (tracked) { + bool found = false; + for (size_t tier = 0; tier < SYSTEM_HEALTH_TIER_COUNT; ++tier) { + tier_worker_t *worker = &observed_runtime->workers[tier]; + if (argument == worker) { + found = worker->initialized && worker->runtime == observed_runtime && + worker->tier == (system_health_sampling_tier_t)tier; + break; + } + } + if (!found || created >= SYSTEM_HEALTH_TIER_COUNT) atomic_fetch_add(&bad_worker, 1); + } + int result = __real_pthread_create(thread, attr, entry, argument); + if (tracked && result == 0 && created < SYSTEM_HEALTH_TIER_COUNT) + owned_threads[created++] = *thread; + return result; +} +int __real_pthread_join(pthread_t, void **); +int __wrap_pthread_join(pthread_t thread, void **result) { + int status = __real_pthread_join(thread, result); + if (observe && status == 0) + for (unsigned i = 0; i < created; ++i) + if (pthread_equal(thread, owned_threads[i])) { joined++; break; } + return status; +} +int __real_pthread_cond_timedwait(pthread_cond_t *, pthread_mutex_t *, const struct timespec *); +int __wrap_pthread_cond_timedwait(pthread_cond_t *condition, pthread_mutex_t *lock, + const struct timespec *deadline) { + if (observe && observed_runtime) { + bool found = false; + for (size_t tier = 0; tier < SYSTEM_HEALTH_TIER_COUNT; ++tier) { + tier_worker_t *worker = &observed_runtime->workers[tier]; + if (condition != &worker->wake) continue; + found = true; + uint64_t expected_ns = (uint64_t)last_monotonic.tv_nsec + + (uint64_t)observed_runtime->options.tier_interval_ms[tier] * UINT64_C(1000000); + if (lock != &worker->lock || !have_monotonic || + deadline->tv_sec != last_monotonic.tv_sec + (time_t)(expected_ns / UINT64_C(1000000000)) || + deadline->tv_nsec != (long)(expected_ns % UINT64_C(1000000000))) + atomic_fetch_add(&bad_worker, 1); + atomic_fetch_or(&wait_mask, 1U << tier); + break; + } + if (!found) atomic_fetch_add(&bad_worker, 1); + } + return __real_pthread_cond_timedwait(condition, lock, deadline); +} + +static system_health_options_t configuration(void) { + system_health_options_t options; + system_health_options_defaults(&options); + options.register_builtin_collectors = false; + strcpy(options.hardware_provider, "disabled"); + strcpy(options.proc_root, "/nonexistent-t7-59-proc"); + strcpy(options.sys_root, "/nonexistent-t7-59-sys"); + strcpy(options.cgroup_root, "/nonexistent-t7-59-cgroup"); + for (size_t tier = 0; tier < SYSTEM_HEALTH_TIER_COUNT; ++tier) { + options.tier_interval_ms[tier] = 13001U + (uint32_t)tier * 4001U; + options.collector_deadline_ms[tier] = 1000; + } + return options; +} +static int fake_collect(void *state, const system_health_collect_context_t *context, + system_health_observation_sink_t *sink) { + (void)context; (void)sink; + atomic_fetch_add((atomic_uint *)state, 1); + return 0; +} +static system_health_collector_t descriptor(unsigned tier) { + system_health_collector_t collector = {0}; + snprintf(collector.name, sizeof(collector.name), "fixture.%u", tier); + collector.tier = (system_health_sampling_tier_t)tier; + collector.scope = SYSTEM_HEALTH_SCOPE_HOST; + collector.collect = fake_collect; + collector.state = &calls[tier]; + collector.interval_seconds = 1; + collector.stale_after_seconds = 3; + return collector; +} +static void initialize(const system_health_options_t *options) { + TEST_ASSERT_EQUAL_INT(0, system_health_init(options)); + observed_runtime = runtime; + TEST_ASSERT_NOT_NULL(observed_runtime); + TEST_ASSERT_EQUAL_size_t(0, system_health_provider_registry_count()); +} +static void register_fakes(void) { + for (unsigned tier = 0; tier < SYSTEM_HEALTH_TIER_COUNT; ++tier) { + system_health_collector_t collector = descriptor(tier); + TEST_ASSERT_TRUE(system_health_register_collector(&collector)); + } +} +static void start_workers(void) { + observe = true; + TEST_ASSERT_EQUAL_INT(0, system_health_start()); + TEST_ASSERT_EQUAL_UINT(SYSTEM_HEALTH_TIER_COUNT, created); +} +static void wait_for_workers(unsigned minimum_calls) { + uint64_t limit = monotonic_ms() + 2000; + for (;;) { + bool ready = atomic_load(&wait_mask) == (1U << SYSTEM_HEALTH_TIER_COUNT) - 1U; + for (size_t tier = 0; tier < SYSTEM_HEALTH_TIER_COUNT; ++tier) + if (atomic_load(&calls[tier]) < minimum_calls) ready = false; + if (ready) break; + TEST_ASSERT_LESS_THAN_UINT64(limit, monotonic_ms()); + struct timespec pause = {0, 1000000}; nanosleep(&pause, NULL); + } + TEST_ASSERT_EQUAL_UINT(0, atomic_load(&bad_worker)); +} +static void invalid_public_tiers(void) { + const int tiers[] = {-1, (int)SYSTEM_HEALTH_TIER_COUNT, INT_MIN, INT_MAX}; + for (size_t i = 0; i < sizeof(tiers) / sizeof(tiers[0]); ++i) { + system_health_sampling_tier_t tier = (system_health_sampling_tier_t)tiers[i]; + TEST_ASSERT_EQUAL_INT(-1, system_health_collect_tier(tier)); + TEST_ASSERT_FALSE(system_health_request_tier(tier)); + } +} +void setUp(void) { + system_health_shutdown(); + observed_runtime = NULL; observe = false; created = joined = 0; + atomic_store(&wait_mask, 0); atomic_store(&bad_worker, 0); + for (size_t tier = 0; tier < SYSTEM_HEALTH_TIER_COUNT; ++tier) atomic_store(&calls[tier], 0); +} +void tearDown(void) { + system_health_shutdown(); /* Real signal + join; no cancellation or detached workers. */ + observe = false; observed_runtime = NULL; + TEST_ASSERT_NULL(runtime); + TEST_ASSERT_EQUAL_UINT(created, joined); + TEST_ASSERT_EQUAL_UINT(0, atomic_load(&bad_worker)); +} + +static void test_public_invalid_tiers_rejected_without_runtime(void) { invalid_public_tiers(); } +static void test_constructor_assigns_all_and_only_array_indices(void) { + system_health_options_t options = configuration(); + initialize(&options); + for (size_t tier = 0; tier < SYSTEM_HEALTH_TIER_COUNT; ++tier) { + tier_worker_t *worker = &runtime->workers[tier]; + TEST_ASSERT_TRUE(worker->initialized); + TEST_ASSERT_EQUAL_PTR(runtime, worker->runtime); + TEST_ASSERT_EQUAL_INT((int)tier, worker->tier); + TEST_ASSERT_FALSE(worker->started); + uint32_t copied = options.tier_interval_ms[tier]; + options.tier_interval_ms[tier] = 0; + TEST_ASSERT_EQUAL_UINT32(copied, runtime->options.tier_interval_ms[tier]); + } + invalid_public_tiers(); +} +static void test_invalid_collector_tiers_do_not_modify_worker_registry(void) { + system_health_options_t options = configuration(); initialize(&options); + const int tiers[] = {-1, (int)SYSTEM_HEALTH_TIER_COUNT, INT_MIN, INT_MAX}; + for (size_t i = 0; i < sizeof(tiers) / sizeof(tiers[0]); ++i) { + system_health_collector_t collector = descriptor(0); + collector.tier = (system_health_sampling_tier_t)tiers[i]; + TEST_ASSERT_FALSE(system_health_register_collector(&collector)); + } + TEST_ASSERT_EQUAL_size_t(0, runtime->collector_count); + for (size_t tier = 0; tier < SYSTEM_HEALTH_TIER_COUNT; ++tier) + TEST_ASSERT_EQUAL_INT((int)tier, runtime->workers[tier].tier); +} +static void test_each_zero_interval_or_deadline_rejected_before_worker_allocation(void) { + for (size_t tier = 0; tier < SYSTEM_HEALTH_TIER_COUNT; ++tier) { + system_health_options_t options = configuration(); + options.tier_interval_ms[tier] = 0; + TEST_ASSERT_EQUAL_INT(-1, system_health_init(&options)); TEST_ASSERT_NULL(runtime); + options = configuration(); options.collector_deadline_ms[tier] = 0; + TEST_ASSERT_EQUAL_INT(-1, system_health_init(&options)); TEST_ASSERT_NULL(runtime); + } +} +static void test_actual_workers_read_own_interval_and_support_bounded_requests(void) { + system_health_options_t options = configuration(); initialize(&options); register_fakes(); + start_workers(); wait_for_workers(1); + invalid_public_tiers(); + for (size_t tier = 0; tier < SYSTEM_HEALTH_TIER_COUNT; ++tier) + TEST_ASSERT_TRUE(system_health_request_tier((system_health_sampling_tier_t)tier)); + wait_for_workers(2); + TEST_ASSERT_EQUAL_INT(0, system_health_start()); + TEST_ASSERT_EQUAL_UINT(SYSTEM_HEALTH_TIER_COUNT, created); +} +static void test_interval_extremes_use_valid_worker_slots_and_normalized_deadlines(void) { + system_health_options_t options = configuration(); + options.tier_interval_ms[0] = 1; + options.tier_interval_ms[1] = 999; + options.tier_interval_ms[2] = 1000; + options.tier_interval_ms[3] = UINT32_MAX; + initialize(&options); register_fakes(); start_workers(); wait_for_workers(1); +} +static void test_disabled_runtime_has_no_worker_arguments_or_threads(void) { + system_health_options_t options = configuration(); options.enabled = false; + TEST_ASSERT_EQUAL_INT(0, system_health_init(&options)); + TEST_ASSERT_NULL(runtime); observe = true; + TEST_ASSERT_EQUAL_INT(0, system_health_start()); + TEST_ASSERT_EQUAL_UINT(0, created); + invalid_public_tiers(); +} + +int main(void) { + UNITY_BEGIN(); + RUN_TEST(test_public_invalid_tiers_rejected_without_runtime); + RUN_TEST(test_constructor_assigns_all_and_only_array_indices); + RUN_TEST(test_invalid_collector_tiers_do_not_modify_worker_registry); + RUN_TEST(test_each_zero_interval_or_deadline_rejected_before_worker_allocation); + RUN_TEST(test_actual_workers_read_own_interval_and_support_bounded_requests); + RUN_TEST(test_interval_extremes_use_valid_worker_slots_and_normalized_deadlines); + RUN_TEST(test_disabled_runtime_has_no_worker_arguments_or_threads); + return UNITY_END(); +} diff --git a/tests/unit/test_t7_59_start_failure.c b/tests/unit/test_t7_59_start_failure.c new file mode 100644 index 000000000..89b728ee3 --- /dev/null +++ b/tests/unit/test_t7_59_start_failure.c @@ -0,0 +1,267 @@ +#define _POSIX_C_SOURCE 200809L +/* Separate lifecycle hypothesis; never supplies an invalid worker tier. */ +#include "../../src/telemetry/system_health.c" +#include "unity.h" +#include +#include +#include + +typedef struct { void *(*entry)(void *); void *argument; } start_argument_t; +static start_argument_t starts[SYSTEM_HEALTH_TIER_COUNT]; +static int fail_after, event_fd = -1; +static atomic_uint created, joined, joined_mask, bad_join; +static pthread_t owned_threads[2 * SYSTEM_HEALTH_TIER_COUNT]; +static atomic_bool fault_consumed; +static int contender_kind; +static atomic_bool contender_go, contender_at_lock, contender_done; +static int contender_result; +static atomic_uint reached_lock; +static _Thread_local bool is_worker, is_contender, reported_lock; +static _Thread_local tier_worker_t *current_worker; +static pid_t owned_child; +static int owned_pipe = -1; + +static void event(char value) { + if (event_fd >= 0 && write(event_fd, &value, 1) != 1) _exit(90); +} +int __real_pthread_mutex_lock(pthread_mutex_t *); +int __wrap_pthread_mutex_lock(pthread_mutex_t *lock) { + if (is_worker && !reported_lock && lock == &lifecycle_lock) { + reported_lock = true; + event('L'); + atomic_fetch_add(&reached_lock, 1); + } + if (is_contender && lock == &lifecycle_lock && !reported_lock) { + reported_lock = true; event('C'); atomic_store(&contender_at_lock, true); + } + return __real_pthread_mutex_lock(lock); +} +static void *worker_entry(void *opaque) { + start_argument_t *start = opaque; + is_worker = true; current_worker = start->argument; + return start->entry(start->argument); +} +int __real_pthread_cond_wait(pthread_cond_t *, pthread_mutex_t *); +int __wrap_pthread_cond_wait(pthread_cond_t *condition, pthread_mutex_t *lock) { + /* The fixed startup gate replaces the original contested-lock boundary. + * Keep the original lifecycle-lock seam too: unchanged source stays RED. */ + if (is_worker && !reported_lock && current_worker && + condition == ¤t_worker->wake && lock == ¤t_worker->lock) { + reported_lock = true; event('G'); atomic_fetch_add(&reached_lock, 1); + } + return __real_pthread_cond_wait(condition, lock); +} +static void await_boundary(atomic_bool *flag) { + uint64_t deadline = monotonic_ms() + 1000; + while (!atomic_load(flag)) { + if (monotonic_ms() >= deadline) _exit(93); + struct timespec pause = {0, 1000000}; nanosleep(&pause, NULL); + } +} +static void checkpoint(unsigned workers) { + uint64_t deadline = monotonic_ms() + 1000; + while (atomic_load(&reached_lock) < workers) { + if (monotonic_ms() >= deadline) _exit(91); + struct timespec pause = {0, 1000000}; nanosleep(&pause, NULL); + } + if (contender_kind) { + atomic_store(&contender_go, true); + await_boundary(&contender_at_lock); + if (atomic_load(&contender_done)) _exit(94); + } +} +int __real_pthread_create(pthread_t *, const pthread_attr_t *, void *(*)(void *), void *); +int __wrap_pthread_create(pthread_t *thread, const pthread_attr_t *attr, + void *(*entry)(void *), void *argument) { + if (entry != tier_worker_main) return __real_pthread_create(thread, attr, entry, argument); + tier_worker_t *worker = argument; + unsigned tier = (unsigned)worker->tier; + if (tier >= SYSTEM_HEALTH_TIER_COUNT) _exit(92); + if (fail_after >= 0 && tier == (unsigned)fail_after && + !atomic_exchange(&fault_consumed, true)) { + checkpoint(tier); + event('F'); + return EAGAIN; + } + starts[tier] = (start_argument_t){entry, argument}; + int result = __real_pthread_create(thread, attr, worker_entry, &starts[tier]); + if (result == 0) { + unsigned index = atomic_load(&created); + if (index >= 2 * SYSTEM_HEALTH_TIER_COUNT) _exit(95); + owned_threads[index] = *thread; + atomic_store(&created, index + 1); + if (contender_kind && fail_after < 0 && tier == SYSTEM_HEALTH_TIER_COUNT - 1) + checkpoint(SYSTEM_HEALTH_TIER_COUNT); + } + return result; +} +int __real_pthread_join(pthread_t, void **); +int __wrap_pthread_join(pthread_t thread, void **result) { + event('J'); + unsigned bit = 0; + for (unsigned i = atomic_load(&created); i > 0; --i) { + unsigned candidate = 1U << (i - 1); + if (!(atomic_load(&joined_mask) & candidate) && pthread_equal(thread, owned_threads[i - 1])) { + bit = candidate; break; + } + } + int status = __real_pthread_join(thread, result); + if (status == 0 && bit && !(atomic_fetch_or(&joined_mask, bit) & bit)) + atomic_fetch_add(&joined, 1); + else atomic_fetch_add(&bad_join, 1); + return status; +} +static void *contender_entry(void *unused) { + (void)unused; is_contender = true; + await_boundary(&contender_go); + if (contender_kind == 1) contender_result = system_health_start(); + else { system_health_shutdown(); contender_result = 0; } + atomic_store(&contender_done, true); + return NULL; +} + +static int fake_collect(void *state, const system_health_collect_context_t *context, + system_health_observation_sink_t *sink) { + (void)state; (void)context; (void)sink; + return 0; +} +static int child_scenario(int failure, int concurrent) { + system_health_options_t options; + system_health_options_defaults(&options); + options.register_builtin_collectors = false; + strcpy(options.hardware_provider, "disabled"); + for (size_t tier = 0; tier < SYSTEM_HEALTH_TIER_COUNT; ++tier) + options.tier_interval_ms[tier] = 60000; + if (system_health_init(&options) != 0 || system_health_provider_registry_count() != 0) return 10; + for (unsigned tier = 0; tier < SYSTEM_HEALTH_TIER_COUNT; ++tier) { + system_health_collector_t collector = {0}; + snprintf(collector.name, sizeof(collector.name), "fixture.%u", tier); + collector.tier = (system_health_sampling_tier_t)tier; + collector.scope = SYSTEM_HEALTH_SCOPE_HOST; + collector.collect = fake_collect; + collector.interval_seconds = 1; + collector.stale_after_seconds = 3; + if (!system_health_register_collector(&collector)) return 11; + } + fail_after = failure; created = joined = 0; atomic_store(&reached_lock, 0); + atomic_store(&joined_mask, 0); atomic_store(&bad_join, 0); + atomic_store(&fault_consumed, false); + contender_kind = concurrent; + atomic_store(&contender_go, false); atomic_store(&contender_at_lock, false); + atomic_store(&contender_done, false); contender_result = -99; + pthread_t contender; + if (concurrent && __real_pthread_create(&contender, NULL, contender_entry, NULL) != 0) return 16; + int result = system_health_start(); + if (concurrent) { + if (result != (failure >= 0 ? -1 : 0)) return 17; + if (__real_pthread_join(contender, NULL) != 0 || !atomic_load(&contender_done) || + contender_result != 0 || !atomic_load(&contender_at_lock)) return 18; + unsigned expected = (failure >= 0 ? (unsigned)failure : SYSTEM_HEALTH_TIER_COUNT); + if (concurrent == 1) { + if (!runtime || !runtime->started) return 19; + if (failure >= 0) expected += SYSTEM_HEALTH_TIER_COUNT; + system_health_shutdown(); + } + if (runtime || atomic_load(&created) != expected || atomic_load(&joined) != expected || + atomic_load(&bad_join) || atomic_load(&joined_mask) != (1U << expected) - 1) return 20; + return 0; + } + if (failure >= 0) { + if (result != -1 || created != (unsigned)failure || joined != created || + atomic_load(&runtime->running) || runtime->started) return 12; + for (size_t tier = 0; tier < SYSTEM_HEALTH_TIER_COUNT; ++tier) + if (runtime->workers[tier].started) return 13; + /* Retry the same initialized runtime, then require actual signal/join + * cleanup. No reset/corruption of production worker state to help it. */ + fail_after = -1; created = joined = 0; atomic_store(&reached_lock, 0); + atomic_store(&joined_mask, 0); + result = system_health_start(); + } + if (result != 0 || created != SYSTEM_HEALTH_TIER_COUNT) return 14; + system_health_shutdown(); + if (runtime || joined != created || atomic_load(&bad_join) || + atomic_load(&joined_mask) != (1U << atomic_load(&created)) - 1) return 15; + return 0; +} +static void run_owned_child(int failure, int concurrent) { + int pipes[2]; TEST_ASSERT_EQUAL_INT(0, pipe(pipes)); + fflush(NULL); + owned_child = fork(); + if (owned_child == 0) { + close(pipes[0]); event_fd = pipes[1]; + int result = child_scenario(failure, concurrent); + close(event_fd); event_fd = -1; + exit(result); /* Run sanitizer leak checks on normal child completion. */ + } + close(pipes[1]); owned_pipe = pipes[0]; + TEST_ASSERT_GREATER_THAN_INT(0, owned_child); + TEST_ASSERT_NOT_EQUAL(-1, fcntl(owned_pipe, F_SETFL, O_NONBLOCK)); + uint64_t deadline = monotonic_ms() + 3000; + int status = 0; bool timeout = false; + for (;;) { + pid_t result = waitpid(owned_child, &status, WNOHANG); + if (result == owned_child) break; + TEST_ASSERT_EQUAL_INT(0, result); + if (monotonic_ms() >= deadline) { + timeout = true; + /* Still an unreaped owned child: PID cannot have been recycled. */ + TEST_ASSERT_EQUAL_INT(0, kill(owned_child, SIGKILL)); + TEST_ASSERT_EQUAL_INT(owned_child, waitpid(owned_child, &status, 0)); + break; + } + struct timespec pause = {0, 1000000}; nanosleep(&pause, NULL); + } + owned_child = 0; + unsigned locks = 0, gates = 0, faults = 0, joins = 0, contenders = 0; + char bytes[128]; ssize_t count; + while ((count = read(owned_pipe, bytes, sizeof(bytes))) > 0) + for (ssize_t i = 0; i < count; ++i) { + if (bytes[i] == 'L') locks++; + if (bytes[i] == 'G') gates++; + if (bytes[i] == 'C') contenders++; + if (bytes[i] == 'F') faults++; + if (bytes[i] == 'J') joins++; + } + TEST_ASSERT_EQUAL_INT(0, close(owned_pipe)); owned_pipe = -1; + printf("startup failure=%d concurrent=%d: reached_lock=%u reached_gate=%u contender=%u injected=%u joins_entered=%u timeout=%d status=%d\n", + failure, concurrent, locks, gates, contenders, faults, joins, timeout, status); + if (failure >= 0) TEST_ASSERT_EQUAL_UINT(1, faults); + if (failure > 0) { + TEST_ASSERT_GREATER_OR_EQUAL_UINT((unsigned)failure, locks + gates); + TEST_ASSERT_GREATER_OR_EQUAL_UINT(1, joins); + } + if (concurrent) TEST_ASSERT_EQUAL_UINT(1, contenders); + TEST_ASSERT_FALSE_MESSAGE(timeout, "owned child exceeded deadline; containment kill is a failed lifecycle result"); + TEST_ASSERT_TRUE(WIFEXITED(status)); + TEST_ASSERT_EQUAL_INT(0, WEXITSTATUS(status)); +} +void setUp(void) { owned_child = 0; owned_pipe = -1; } +void tearDown(void) { + bool leaked = owned_child > 0 || owned_pipe >= 0; + if (owned_child > 0) { kill(owned_child, SIGKILL); waitpid(owned_child, NULL, 0); owned_child = 0; } + if (owned_pipe >= 0) { close(owned_pipe); owned_pipe = -1; } + TEST_ASSERT_FALSE(leaked); +} +static void test_normal_start_shutdown_joins_every_worker(void) { run_owned_child(-1, 0); } +static void test_first_worker_failure_can_retry_and_shutdown(void) { run_owned_child(0, 0); } +static void test_second_worker_failure_releases_first_and_can_retry(void) { run_owned_child(1, 0); } +static void test_third_worker_failure_releases_prior_workers_and_can_retry(void) { run_owned_child(2, 0); } +static void test_fourth_worker_failure_releases_prior_workers_and_can_retry(void) { run_owned_child(3, 0); } +static void test_partial_failure_serializes_concurrent_retry(void) { run_owned_child(2, 1); } +static void test_partial_failure_serializes_concurrent_shutdown(void) { run_owned_child(2, 2); } +static void test_successful_start_serializes_concurrent_start(void) { run_owned_child(-1, 1); } +static void test_successful_start_serializes_concurrent_shutdown(void) { run_owned_child(-1, 2); } + +int main(void) { + UNITY_BEGIN(); + RUN_TEST(test_normal_start_shutdown_joins_every_worker); + RUN_TEST(test_first_worker_failure_can_retry_and_shutdown); + RUN_TEST(test_second_worker_failure_releases_first_and_can_retry); + RUN_TEST(test_third_worker_failure_releases_prior_workers_and_can_retry); + RUN_TEST(test_fourth_worker_failure_releases_prior_workers_and_can_retry); + RUN_TEST(test_partial_failure_serializes_concurrent_retry); + RUN_TEST(test_partial_failure_serializes_concurrent_shutdown); + RUN_TEST(test_successful_start_serializes_concurrent_start); + RUN_TEST(test_successful_start_serializes_concurrent_shutdown); + return UNITY_END(); +} diff --git a/web/babel.config.cjs b/web/babel.config.cjs index f037a1a2a..b12057830 100644 --- a/web/babel.config.cjs +++ b/web/babel.config.cjs @@ -1,5 +1,7 @@ module.exports = { presets: [ + '@babel/preset-typescript', + ['@babel/preset-react', { runtime: 'automatic', importSource: 'preact' }], [ '@babel/preset-env', { diff --git a/web/eslint-rules/no-duplicate-implementation.js b/web/eslint-rules/no-duplicate-implementation.js new file mode 100644 index 000000000..a019cfe97 --- /dev/null +++ b/web/eslint-rules/no-duplicate-implementation.js @@ -0,0 +1,18 @@ +import { duplicateImplementations } from './source-identity.js'; + +export default { + meta: { + type: 'problem', + docs: { description: 'Reject multiple implementations of one extension-independent module identity' }, + schema: [], + messages: { duplicate: 'Duplicate module implementations: {{files}}. Keep one implementation; a JS bridge must use @ts-check and only re-export its typed sibling.' }, + }, + create(context) { + return { + Program(node) { + const duplicates = duplicateImplementations(context.filename); + if (duplicates.length) context.report({ node, messageId: 'duplicate', data: { files: duplicates.join(', ') } }); + }, + }; + }, +}; diff --git a/web/eslint-rules/no-literal-ui-string.js b/web/eslint-rules/no-literal-ui-string.js index 3ca92ce6e..24630c22e 100644 --- a/web/eslint-rules/no-literal-ui-string.js +++ b/web/eslint-rules/no-literal-ui-string.js @@ -1,12 +1,14 @@ import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; +import { canonicalAllowances, sourceIdentity } from './source-identity.js'; -const baseline = JSON.parse(fs.readFileSync(new URL('../i18n-literal-baseline.json', import.meta.url), 'utf8')); +const baseline = canonicalAllowances(JSON.parse(fs.readFileSync(new URL('../i18n-literal-baseline.json', import.meta.url), 'utf8'))); const componentsRoot = fileURLToPath(new URL('../js/components/', import.meta.url)); const visibleAttributes = new Set(['title', 'aria-label', 'placeholder', 'alt']); function candidate(value) { + if (typeof value !== 'string') return null; const normalized = value.replace(/\s+/g, ' ').trim(); return /[A-Za-z]/.test(normalized) ? normalized : null; } @@ -16,7 +18,14 @@ function candidate(value) { function expressionStrings(node) { if (!node) return []; if (node.type === 'Literal' && typeof node.value === 'string') return [node.value]; - if (node.type === 'TemplateLiteral') return node.quasis.map((part) => part.value.cooked || ''); + if (['TSAsExpression', 'TSTypeAssertion', 'TSNonNullExpression', 'TSSatisfiesExpression', 'ChainExpression'].includes(node.type)) { + return expressionStrings(node.expression); + } + if (node.type === 'TemplateLiteral') { + return [...node.quasis.map((part) => part.value.cooked || ''), ...node.expressions.flatMap(expressionStrings)]; + } + if (node.type === 'ArrayExpression') return node.elements.flatMap(expressionStrings); + if (node.type === 'SequenceExpression') return expressionStrings(node.expressions.at(-1)); if (node.type === 'ConditionalExpression') { return [...expressionStrings(node.consequent), ...expressionStrings(node.alternate)]; } @@ -35,7 +44,7 @@ export default { }, create(context) { const file = path.relative(componentsRoot, context.filename).replaceAll(path.sep, '/'); - const allowed = baseline[file] || {}; + const allowed = baseline.get(sourceIdentity(file)) || {}; const seen = new Map(); function check(node, text) { @@ -51,8 +60,10 @@ export default { return { JSXText(node) { check(node, node.value); }, JSXAttribute(node) { - if (visibleAttributes.has(node.name.name) && node.value?.type === 'Literal') { - check(node, node.value.value); + if (!visibleAttributes.has(node.name.name)) return; + if (node.value?.type === 'Literal') check(node, node.value.value); + if (node.value?.type === 'JSXExpressionContainer') { + for (const value of expressionStrings(node.value.expression)) check(node, value); } }, JSXExpressionContainer(node) { diff --git a/web/eslint-rules/source-identity.js b/web/eslint-rules/source-identity.js new file mode 100644 index 000000000..e6d701f98 --- /dev/null +++ b/web/eslint-rules/source-identity.js @@ -0,0 +1,54 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import tseslint from 'typescript-eslint'; + +const extensions = ['.js', '.jsx', '.ts', '.tsx']; + +// Extensions describe the compiler, not a new implementation or allowance. +export function sourceIdentity(filename) { + return filename.replaceAll(path.sep, '/').replace(/\.[jt]sx?$/, ''); +} + +export function canonicalAllowances(baseline) { + const result = new Map(); + for (const [filename, counts] of Object.entries(baseline)) { + const identity = sourceIdentity(filename); + if (result.has(identity)) throw new Error(`Duplicate i18n baseline identity: ${identity}`); + result.set(identity, counts); + } + return result; +} + +function checkedReExport(filename, siblings) { + if (!/\.jsx?$/.test(filename)) return false; + try { + const ast = tseslint.parser.parseForESLint(fs.readFileSync(filename, 'utf8'), { + filePath: filename, + sourceType: 'module', + ecmaVersion: 'latest', + ecmaFeatures: { jsx: true }, + comment: true, + }).ast; + const comments = ast.comments || []; + if (!comments.some(({ value }) => /^\s*@ts-check\b/.test(value))) return false; + if (comments.some(({ value }) => /@ts-(?:nocheck|ignore|expect-error)\b/.test(value))) return false; + return ast.body.length > 0 && ast.body.every((statement) => { + if (!['ExportNamedDeclaration', 'ExportAllDeclaration'].includes(statement.type) + || statement.declaration || typeof statement.source?.value !== 'string') return false; + const target = path.resolve(path.dirname(filename), statement.source.value); + return /\.tsx?$/.test(target) && siblings.includes(target) + && sourceIdentity(filename) === sourceIdentity(target); + }); + } catch { + // A broken or unreadable sibling cannot be treated as a safe bridge. + return false; + } +} + +export function duplicateImplementations(filename) { + const identity = filename.replace(/\.[jt]sx?$/, ''); + const siblings = extensions.map((extension) => identity + extension).filter((file) => fs.existsSync(file)); + if (siblings.length < 2) return []; + const implementations = siblings.filter((file) => !checkedReExport(file, siblings)); + return implementations.length > 1 ? implementations : []; +} diff --git a/web/eslint.config.js b/web/eslint.config.js index 8696d77c0..2e12ac919 100644 --- a/web/eslint.config.js +++ b/web/eslint.config.js @@ -2,13 +2,14 @@ * ESLint configuration for the LightNVR web UI. * * Adds eslint-plugin-jsx-a11y (recommended ruleset) as part of the WCAG 2.2 AA - * audit in https://github.com/opensensor/lightNVR/issues/647. All rules start - * in warn mode so CI reports violations without failing the build; switch rules - * to error as each finding category in the issue is closed. + * audit in https://github.com/opensensor/lightNVR/issues/647. Legacy JS findings remain warnings recorded in the findings inventory. + * Migrated TS/TSX sources enforce accessibility, hooks and type-escape rules. */ +import tseslint from 'typescript-eslint'; import jsxA11y from 'eslint-plugin-jsx-a11y'; import reactHooks from 'eslint-plugin-react-hooks'; import noLiteralUiString from './eslint-rules/no-literal-ui-string.js'; +import noDuplicateImplementation from './eslint-rules/no-duplicate-implementation.js'; const recommendedRules = jsxA11y.flatConfigs.recommended.rules; @@ -21,13 +22,17 @@ export default [ ignores: ['dist/**', 'node_modules/**', 'coverage/**'], }, { - files: ['js/**/*.js', 'js/**/*.jsx'], + files: ['js/**/*.{js,jsx,ts,tsx}'], linterOptions: { // Targeted disable comments (e.g. react-hooks) belong to configs outside // this a11y lint; don't flag them as unused here. reportUnusedDisableDirectives: 'off', }, - plugins: { 'jsx-a11y': jsxA11y, 'react-hooks': reactHooks }, + plugins: { + 'jsx-a11y': jsxA11y, + 'react-hooks': reactHooks, + 'lightnvr-source': { rules: { 'no-duplicate-implementation': noDuplicateImplementation } }, + }, languageOptions: { ...jsxA11y.flatConfigs.recommended.languageOptions, sourceType: 'module', @@ -39,6 +44,7 @@ export default [ }, rules: { ...a11yWarnRules, + 'lightnvr-source/no-duplicate-implementation': 'error', // The plugin is registered only so pre-existing targeted // eslint-disable-next-line comments referencing react-hooks rules // resolve; its rules stay off while this config tracks the a11y audit. @@ -47,10 +53,25 @@ export default [ }, }, { - files: ['js/components/**/*.js', 'js/components/**/*.jsx'], + files: ['js/components/**/*.{js,jsx,ts,tsx}'], // Existing technical labels and examples are counted by file in the // baseline. New JSX text or visible-attribute literals fail lint. plugins: { 'lightnvr-i18n': { rules: { 'no-literal-ui-string': noLiteralUiString } } }, rules: { 'lightnvr-i18n/no-literal-ui-string': 'error' }, }, + { + files: ['js/**/*.{ts,tsx}'], + languageOptions: { parser: tseslint.parser }, + plugins: { '@typescript-eslint': tseslint.plugin }, + linterOptions: { noInlineConfig: true, reportUnusedDisableDirectives: 'error' }, + rules: { + ...Object.fromEntries(Object.keys(recommendedRules).map((name) => [name, 'error'])), + 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/exhaustive-deps': 'error', + '@typescript-eslint/no-explicit-any': 'error', + '@typescript-eslint/ban-ts-comment': ['error', { + 'ts-check': false, 'ts-nocheck': true, 'ts-ignore': true, 'ts-expect-error': true, + }], + }, + }, ]; diff --git a/web/jest.config.cjs b/web/jest.config.cjs index dceb2d23d..20a1a86e8 100644 --- a/web/jest.config.cjs +++ b/web/jest.config.cjs @@ -3,7 +3,7 @@ // excluded here and run via `npm run test:e2e` (jest.e2e.config.cjs). module.exports = { testMatch: [ - '**/tests/**/*.spec.js' + '**/tests/**/*.spec.[jt]s?(x)' ], testPathIgnorePatterns: [ '/node_modules/', @@ -13,6 +13,6 @@ module.exports = { setupFilesAfterEnv: ['./tests/setup.cjs'], testTimeout: 30000, transform: { - '^.+\\.jsx?$': ['babel-jest', { configFile: './babel.config.cjs' }] + '^.+\\.[jt]sx?$': ['babel-jest', { configFile: './babel.config.cjs' }] } }; diff --git a/web/jest.e2e.config.cjs b/web/jest.e2e.config.cjs index 331eb8985..bbb3e7f91 100644 --- a/web/jest.e2e.config.cjs +++ b/web/jest.e2e.config.cjs @@ -9,12 +9,12 @@ // E2E_HEADLESS 'false' to watch the browser; headless otherwise module.exports = { testMatch: [ - '**/tests/e2e/**/*.spec.js' + '**/tests/e2e/**/*.spec.[jt]s?(x)' ], testEnvironment: 'node', setupFilesAfterEnv: ['./tests/setup.cjs'], testTimeout: 60000, transform: { - '^.+\\.jsx?$': ['babel-jest', { configFile: './babel.config.cjs' }] + '^.+\\.[jt]sx?$': ['babel-jest', { configFile: './babel.config.cjs' }] } }; diff --git a/web/js/api/generated/package.json b/web/js/api/generated/package.json new file mode 100644 index 000000000..7f1fc33dd --- /dev/null +++ b/web/js/api/generated/package.json @@ -0,0 +1,4 @@ +{ + "type": "module", + "sideEffects": false +} diff --git a/web/js/api/generated/types.ts b/web/js/api/generated/types.ts new file mode 100644 index 000000000..d232c0ad0 --- /dev/null +++ b/web/js/api/generated/types.ts @@ -0,0 +1,894 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export interface paths { + "/api/auth/verify": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Current principal (role and demo-mode gating for the Streams UI) + * @description Three 200 shapes: authentication disabled (synthetic admin), a real authenticated user, or demo-mode anonymous viewer. Uses httpd_get_authenticated_user (scoped API tokens are not accepted here although the streams handlers accept them). 401 also clears the session cookie. An authenticated user in demo mode receives no `demo_mode` key (F-T11-13). + */ + get: operations["verifyAuth"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/client-config": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Viewer-safe runtime playback configuration */ + get: operations["getClientConfig"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/detection/models": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Detection model options for the edit form + * @description 401 unauthenticated; 403 unless the principal has global camera.configure or camera.configure on at least one camera. Viewers therefore receive 403 and the UI shows no models (D-04). + */ + get: operations["listDetectionModels"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/streams": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List streams (paged summary for the pilot; legacy full list otherwise) + * @description Authentication is checked first (401). With `summary=true` (or `1`) the paged summary variant is returned; its row shape depends on `surface`. Rows are filtered per camera: `admin`/`live` require live.view, `lpr` requires both lpr.search and lpr.read. `can_configure` is the per-camera camera.configure decision. Query parameters are validated in this order: page/page_size, sort_by, sort_order, surface, availability. Without `summary`, the legacy unbounded array of full stream objects is returned (out of pilot; no current web/js caller) and every other query parameter is ignored. + */ + get: operations["listStreams"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/streams/{name}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + /** + * Update stream configuration (asynchronous apply) + * @description Order of checks: path decode (400 "Invalid request path"), stream lookup (404 "Stream not found", BEFORE authentication), configuration read (500), camera.configure authorization (401/404 "Camera not found"/403) BEFORE the body is parsed, then body parse (400 "Invalid JSON in request body" for an empty or unparsable body). Fields are applied in handler order; the first failing validation returns its error and nothing is stored. Present keys with the wrong JSON type are silently ignored (F-T11-05). A valid JSON body that is not an object (e.g. `null`, `[]`) is accepted as a no-op 202 (F-T11-04). If `isOnvif` is omitted the server recomputes it from whether `url` contains "onvif" (F-T11-11). When the effective isOnvif is true the handler copies onvif_username/onvif_password/onvif_profile, moves URL credentials into them, and runs an ONVIF connection test inline before responding; when false those three keys are ignored. `set_privacy_mode` short-circuits: after all other keys were parsed and validated (and `enable_disabled` already executed) it writes only privacy_mode and answers 200; other edits in the same body are discarded (F-T11-12). Otherwise the response is 202 and a detached worker applies the change; the 202 does not prove persistence. Errors in the 400 set: "playback_transport is invalid", "eptz_config must be a JSON string", "eptz_config exceeds size limit", "eptz_config is not a supported version-1 equidistant ceiling configuration", "retention_days must be between -1 and 365", "detection_retention_days must be between -1 and 365", "detection_url must use http, https, rtsp, or rtsps", "publish_url destinations must be rtmp:// or rtmps:// URLs". 413 set: "detection_url exceeds size limit", "publish_url exceeds size limit". + */ + put: operations["updateStream"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/streams/{name}/full": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Full stream detail used by the edit form + * @description Order of checks: path decode (400 "Invalid request path"), stream lookup (404 "Stream not found", BEFORE authentication), configuration read (500), authentication (401), fleet row (404 "Camera not found"), live.view (403). Callers with live.view but without camera.configure receive the live-only redacted variant; `can_control_privacy` equals the camera.configure decision and selects the variant. The response is wrapped as `{"stream": {...}}` (GET /api/streams/{name}, out of pilot, returns the same object unwrapped). The handler never emits `detection_zones` (D-02); zones come from GET /api/streams/{name}/zones. + */ + get: operations["getStreamFull"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/streams/{name}/zones": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Detection zones for a stream (read by the edit modal and ZoneEditor) + * @description No pre-auth existence check: 401 first, then 404 "Camera not found" for unknown names, 403 without live.view. Path decode failure is 400 "Invalid stream name". POST/DELETE on this path are out of pilot. + */ + get: operations["getStreamZones"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} +export type webhooks = Record; +export interface components { + schemas: { + /** @enum {string} */ + AuthRole: "admin" | "user" | "viewer" | "api" | "unknown"; + AuthVerifyAuthDisabled: { + auth_absolute_timeout_hours: number; + /** @constant */ + auth_enabled: false; + auth_timeout_hours: number; + /** @constant */ + authenticated: true; + /** @constant */ + must_change_password: false; + /** @constant */ + role: "admin"; + trusted_device_days: number; + /** @constant */ + username: "admin"; + }; + AuthVerifyAuthenticated: { + auth_absolute_timeout_hours: number; + /** @constant */ + auth_enabled: true; + auth_timeout_hours: number; + /** @constant */ + authenticated: true; + email: string; + id: number; + is_active: boolean; + must_change_password: boolean; + password_change_locked: boolean; + role: components["schemas"]["AuthRole"]; + role_id: number; + trusted_device_days: number; + username: string; + }; + AuthVerifyDemo: { + /** @constant */ + authenticated: false; + /** @constant */ + demo_mode: true; + /** @constant */ + must_change_password: false; + /** @constant */ + role: "viewer"; + /** @constant */ + username: "demo"; + }; + AuthVerifyResponse: components["schemas"]["AuthVerifyAuthenticated"] | components["schemas"]["AuthVerifyAuthDisabled"] | components["schemas"]["AuthVerifyDemo"]; + /** @enum {string} */ + Availability: "live" | "offline" | "never_connected" | "disabled"; + /** @description Immutable fleet identity (36-char UUID). Legacy rows created before camera UUIDs existed may carry an empty string. */ + CameraUuid: string; + ClientConfig: { + audio_disabled: boolean; + auto_disabled: boolean; + default_playback_transport: components["schemas"]["PlaybackTransport"]; + demo_mode: boolean; + generate_thumbnails: boolean; + go2rtc_api_port: components["schemas"]["Int32"]; + go2rtc_available: boolean; + go2rtc_base_path: string; + go2rtc_enabled: boolean; + go2rtc_force_native_hls: boolean; + hls_disabled: boolean; + mse_disabled: boolean; + thumbnails_per_recording: components["schemas"]["Int32"]; + web_auth_enabled: boolean; + webrtc_connection_timeout_ms: components["schemas"]["Int32"]; + webrtc_disabled: boolean; + webrtc_ice_recovery_timeout_ms: components["schemas"]["Int32"]; + }; + /** + * @description Fleet health first; when health is unknown and the stream is enabled, the stream-state/UDT status is used (adds Starting/Stopping/Reconnecting). + * @enum {string} + */ + DetailStatus: "Running" | "Degraded" | "Error" | "Stopped" | "Unknown" | "Starting" | "Stopping" | "Reconnecting"; + DetectionModel: { + /** @description Built-ins only. */ + description?: string; + /** @description Built-ins use motion, api-detection, onvif; file models use the absolute file path. */ + id: string; + name: string; + path: string; + /** @description File size in bytes (file models only). */ + size?: number; + supported: boolean; + /** @description Observed values include builtin, api, onvif, sod, sod_realnet, tflite. */ + type: string; + unsupported_reason?: string; + }; + DetectionModelsResponse: { + count: number; + litert_available: boolean; + /** + * @description Present only when the models directory cannot be opened. + * @constant + */ + message?: "Models directory does not exist"; + models: components["schemas"]["DetectionModel"][]; + sod_available: boolean; + }; + /** @description "" (use main stream) or a URL whose scheme is http, https, rtsp or rtsps (case-insensitive prefix check). Other schemes: 400. >= 512 bytes: 413 (scheme is checked first). null clears. */ + DetectionUrl: string | null; + DetectionZone: { + color: string; + enabled: boolean; + filter_classes: string; + id: string; + /** @description 0 = use the stream default. */ + min_confidence: number; + name: string; + polygon: components["schemas"]["ZonePoint"][]; + stream_name: components["schemas"]["StreamName"]; + }; + /** + * @description Redacted value. The server emits an empty string in place of a sensitive value. + * @constant + */ + EmptyString: ""; + /** @description Unix epoch seconds (int64). `0` means "never observed". */ + EpochSecondsOrZero: number; + /** @description "" (disabled) or JSON text of a version-1 equidistant ceiling dewarp document, at most 1023 bytes. The server stores it opaquely after validating; the document schema is EptzConfigV1. */ + EptzConfigString: string; + EptzConfigV1: { + centerX: number; + centerY: number; + defaultTilt: number; + defaultViewFov: number; + defaultYaw: number; + fov: number; + /** @constant */ + mount: "ceiling"; + /** @constant */ + projection: "equidistant"; + radius: number; + rotation: number; + /** @constant */ + version: 1; + }; + /** @description Error envelope used for every non-2xx JSON response in the pilot. Produced only by http_response_set_json_error(), which emits exactly one key. Clients must not depend on the message text for control flow except where an operation documents a specific message. */ + ErrorResponse: { + error: string; + }; + /** @description 168 booleans indexed `day * 24 + hour` in server local time, where day 0 = Sunday ... 6 = Saturday (struct tm tm_wday). Responses always carry exactly 168 entries. On PUT, an array whose length is not 168 is silently ignored (no error) and any entry other than JSON `true` is stored as false (finding candidate F-T11-07). */ + HourOfWeekSchedule: boolean[]; + /** @description C `int` read through cJSON `valueint` (fractional values are truncated, out-of-range values saturate). Used where the server performs no range validation of its own. */ + Int32: number; + /** @description Out of pilot (inventoried, not contract-tested by T18): GET /api/streams without summary. Bare unbounded array of the same objects as the /full detail, each redacted per camera. */ + LegacyStreamList: components["schemas"]["StreamDetail"][]; + /** @description Clearable text field. JSON null clears the stored value (same as ""). */ + NullableClearableString: string | null; + /** + * @description Responses substitute `auto` when the stored value is invalid, so a response never carries a value outside this enum. + * @enum {string} + */ + PlaybackTransport: "auto" | "webrtc_only" | "mse_only" | "hls_only" | "webrtc_then_mse" | "mse_then_hls"; + /** @description "" or a comma/whitespace-separated list of rtmp:// or rtmps:// destinations. Each destination is printable ASCII without quotes or backslash and has at least one character after the scheme. Invalid list: 400; whole string >= 512 bytes: 413 (validity is checked first). null clears. */ + PublishUrlList: string | null; + /** @description Tri-state per-stream retention override in days. `-1` inherits the live global retention value at evaluation time, `0` is explicitly unlimited (also disables tier expiry), and `1..365` is a day override. Writes outside -1..365 are rejected with 400. For `detection_retention_days`, `-1` inherits the *global* retention value, not this stream's `retention_days`. */ + RetentionDays: number; + /** @description Either variant, selected by can_control_privacy (mutually exclusive by const). */ + StreamDetail: components["schemas"]["StreamDetailConfigurable"] | components["schemas"]["StreamDetailLiveOnly"]; + /** @description Non-sensitive fields of the full stream object, emitted for every caller with live.view. Credential-bearing keys (url, onvif_username, onvif_password, admin_url, go2rtc_source_override, sub_stream_url, detection_url, publish_url) are declared here only as strings and are NOT required; each variant pins them. */ + StreamDetailBase: { + admin_url?: string; + audio_voice_enhancement: boolean; + backchannel_enabled: boolean; + camera_uuid: components["schemas"]["CameraUuid"]; + can_control_privacy: boolean; + codec: string; + detection_based_recording: boolean; + detection_interval: components["schemas"]["Int32"]; + detection_model: string; + detection_object_filter: string; + detection_object_filter_list: string; + detection_record_on_schedule: boolean; + detection_recording_schedule: components["schemas"]["HourOfWeekSchedule"]; + detection_retention_days: components["schemas"]["RetentionDays"]; + /** @description Integer percent: (int)(stored_float * 100.0f), truncated toward zero. Not a 0..1 float on the wire. */ + detection_threshold: number; + detection_url?: string; + enabled: boolean; + eptz_config: components["schemas"]["EptzConfigString"]; + /** @description Present only when status is Error and a cause was recorded (currently model-load failures). */ + error_message?: string; + fps: components["schemas"]["Int32"]; + go2rtc_hls_available: boolean; + go2rtc_source_override?: string; + /** @description Presence of a sub-stream; visible to viewers by design. */ + has_sub_stream: boolean; + height: components["schemas"]["Int32"]; + isOnvif: boolean; + location_uuid: components["schemas"]["CameraUuid"]; + /** @description Quota in MB; 0 = unlimited. */ + max_storage_mb: components["schemas"]["Int32"]; + motion_trigger_source: string; + name: components["schemas"]["StreamName"]; + onvif_password?: string; + /** @description 0 = derive from the stream URL. */ + onvif_port: components["schemas"]["Int32"]; + onvif_profile: string; + onvif_username?: string; + playback_transport: components["schemas"]["PlaybackTransport"]; + post_detection_buffer: components["schemas"]["Int32"]; + pre_detection_buffer: components["schemas"]["Int32"]; + priority: components["schemas"]["Int32"]; + privacy_mode: boolean; + protocol: components["schemas"]["StreamProtocol"]; + ptz_enabled: boolean; + ptz_has_home: boolean; + ptz_max_x: components["schemas"]["Int32"]; + ptz_max_y: components["schemas"]["Int32"]; + ptz_max_z: components["schemas"]["Int32"]; + publish_url?: string; + record: boolean; + record_audio: boolean; + record_on_schedule: boolean; + recording_schedule: components["schemas"]["HourOfWeekSchedule"]; + retention_days: components["schemas"]["RetentionDays"]; + segment_duration: components["schemas"]["Int32"]; + status: components["schemas"]["DetailStatus"]; + storage_priority: components["schemas"]["Int32"]; + streaming_enabled: boolean; + sub_stream_url?: string; + tags: string; + tier_critical_multiplier: number; + tier_ephemeral_multiplier: number; + tier_important_multiplier: number; + url?: string; + width: components["schemas"]["Int32"]; + }; + /** @description Admin edit model: caller has camera.configure on this camera (can_control_privacy = true). Sensitive fields carry real values. When separate credentials are in use (isOnvif, or non-empty onvif_username/onvif_password) the server strips userinfo from `url` and surfaces it through the onvif_* fields. */ + StreamDetailConfigurable: components["schemas"]["StreamDetailBase"] & { + /** @constant */ + can_control_privacy?: true; + }; + /** @description Viewer (live.view without camera.configure) model. Every credential-bearing key is OPTIONAL (a server that stops sending it is compatible) but, if present, must be redacted: onvif_* and the administrative URLs must be exactly "", and `url` must be credential-free (no userinfo, no secret-like query parameter). A leaked value therefore fails validation. */ + StreamDetailLiveOnly: components["schemas"]["StreamDetailBase"] & { + admin_url?: components["schemas"]["EmptyString"]; + /** @constant */ + can_control_privacy?: false; + detection_url?: components["schemas"]["EmptyString"]; + go2rtc_source_override?: components["schemas"]["EmptyString"]; + onvif_password?: components["schemas"]["EmptyString"]; + onvif_username?: components["schemas"]["EmptyString"]; + publish_url?: components["schemas"]["EmptyString"]; + sub_stream_url?: components["schemas"]["EmptyString"]; + url?: components["schemas"]["ViewerSafeUrl"]; + }; + StreamFullResponse: { + stream: components["schemas"]["StreamDetail"]; + }; + StreamFullResponseConfigurable: { + stream: components["schemas"]["StreamDetailConfigurable"]; + }; + StreamFullResponseLiveOnly: { + stream: components["schemas"]["StreamDetailLiveOnly"]; + }; + /** @description Stream (camera) name as stored. This is the primary key used in paths. Names created through POST /api/streams must match `^[A-Za-z0-9_-][A-Za-z0-9._-]*$` (is_valid_stream_name), but names that predate that rule or arrive through configuration import can contain other characters, so the read-side schema only bounds the length. */ + StreamName: string; + /** @description Live privacy toggle (used by Live video cells, not the edit form). Modelled as its own closed body because the server discards every other edit when this key is present (F-T11-12). */ + StreamPrivacyModeRequest: { + set_privacy_mode: boolean; + }; + /** @description Always success:true, even if the database write failed (the failure is only logged; F-T11-12). */ + StreamPrivacyModeResponse: { + privacy_mode: boolean; + /** @constant */ + success: true; + }; + /** + * @description Transport protocol: 0 = TCP, 1 = UDP (stream_protocol_t). PUT does not range-check this value (see finding candidate F-T11-06); the contract only permits the two defined values. + * @enum {integer} + */ + StreamProtocol: 0 | 1; + /** @description surface=admin row. `admin_url` appears only with include_admin_url and must be "" when can_configure is false. */ + StreamSummaryAdmin: components["schemas"]["StreamSummaryBase"] & { + admin_url?: string; + }; + /** @description Fields shared by admin and live summary rows (always emitted). */ + StreamSummaryBase: { + availability: components["schemas"]["Availability"]; + camera_uuid: components["schemas"]["CameraUuid"]; + /** @description Per-camera camera.configure decision. Authoritative for edit affordances (D-05). */ + can_configure: boolean; + codec: string; + current_fps: number; + detection_based_recording: boolean; + enabled: boolean; + first_video_at: components["schemas"]["EpochSecondsOrZero"]; + fps: components["schemas"]["Int32"]; + height: components["schemas"]["Int32"]; + isOnvif: boolean; + last_frame_ts: components["schemas"]["EpochSecondsOrZero"]; + last_recording_at: components["schemas"]["EpochSecondsOrZero"]; + location_name: string; + location_path: string; + location_uuid: components["schemas"]["CameraUuid"]; + name: components["schemas"]["StreamName"]; + protocol: components["schemas"]["StreamProtocol"]; + record: boolean; + record_on_schedule: boolean; + recording_active: boolean; + status: components["schemas"]["SummaryStatus"]; + /** @description Comma-separated legacy tags. */ + tags: string; + width: components["schemas"]["Int32"]; + } & components["schemas"]["SummaryForbiddenSensitiveKeys"]; + /** @description surface=live row. Never carries admin_url. */ + StreamSummaryLive: components["schemas"]["StreamSummaryBase"] & { + backchannel_enabled: boolean; + /** @description Equals can_configure for the row. */ + can_control_privacy: boolean; + detection_model: string; + eptz_config: components["schemas"]["EptzConfigString"]; + go2rtc_hls_available: boolean; + has_sub_stream: boolean; + playback_transport: components["schemas"]["PlaybackTransport"]; + privacy_mode: boolean; + ptz_enabled: boolean; + streaming_enabled: boolean; + }; + /** @description surface=lpr row. LPR discovery grants identity only, so this object is closed: any extra key (configuration, location, health) is a policy leak. */ + StreamSummaryLpr: { + camera_uuid: components["schemas"]["CameraUuid"]; + name: components["schemas"]["StreamName"]; + }; + StreamSummaryPageAdmin: components["schemas"]["SummaryPageMeta"] & { + streams: components["schemas"]["StreamSummaryAdmin"][]; + }; + StreamSummaryPageLive: components["schemas"]["SummaryPageMeta"] & { + streams: components["schemas"]["StreamSummaryLive"][]; + }; + StreamSummaryPageLpr: components["schemas"]["SummaryPageMeta"] & { + streams: components["schemas"]["StreamSummaryLpr"][]; + }; + StreamUpdateAccepted: { + /** @constant */ + message: "Stream update request accepted and processing"; + /** @enum {string} */ + onvif_message?: "ONVIF capabilities detected successfully" | "Failed to detect ONVIF capabilities"; + /** + * @description Present when the effective isOnvif was true and the inline ONVIF test ran. + * @enum {string} + */ + onvif_status?: "success" | "error"; + /** @constant */ + success: true; + }; + /** @description Edit-form body. All keys optional (partial update). Closed: the server ignores unknown keys (including `name` and the UI's `is_deleted`, D-01) but the contract rejects them so client drift is visible. Wrong-typed values are silently ignored by the server (F-T11-05). Top-level numeric fields have no server-side range checks except retention (F-T11-06); the JSON embedded in eptz_config is separately range-checked. Over-long strings are silently truncated unless `x-lightnvr-overflow` says otherwise (F-T11-14). */ + StreamUpdateRequest: { + admin_url?: components["schemas"]["NullableClearableString"]; + audio_voice_enhancement?: boolean; + backchannel_enabled?: boolean; + /** @description Codec hint; the UI sends "" (auto), "h264" or "h265". Not validated. */ + codec?: string; + detection_based_recording?: boolean; + detection_interval?: components["schemas"]["Int32"]; + /** @description Copied through a 256-byte buffer although the stored field holds 511 bytes. */ + detection_model?: string; + detection_object_filter?: string; + detection_object_filter_list?: string; + detection_record_on_schedule?: boolean; + detection_recording_schedule?: components["schemas"]["HourOfWeekSchedule"]; + detection_retention_days?: components["schemas"]["RetentionDays"]; + /** @description Percent; stored as value / 100.0 (float). Not range-checked. */ + detection_threshold?: number; + detection_url?: components["schemas"]["DetectionUrl"]; + /** @description Out of pilot (enable/bulk-enable). `true` re-enables a disabled stream synchronously, even when the same body also carries set_privacy_mode. */ + enable_disabled?: boolean; + enabled?: boolean; + eptz_config?: components["schemas"]["EptzConfigString"]; + /** @description Whitespace-only is stored as "". Not YAML-validated by PUT. */ + go2rtc_source_override?: components["schemas"]["NullableClearableString"]; + /** @description When omitted the server recomputes it from `url` containing "onvif" (F-T11-11). The UI always sends it. */ + isOnvif?: boolean; + /** @description 0 = unlimited. */ + max_storage_mb?: components["schemas"]["Int32"]; + /** @description Another stream's name; existence is not checked. */ + motion_trigger_source?: components["schemas"]["NullableClearableString"]; + /** @description Applied only when the effective isOnvif is true; "" clears the stored value (no "unchanged" sentinel). */ + onvif_password?: string; + onvif_port?: components["schemas"]["Int32"]; + /** @description Applied only when the effective isOnvif is true. */ + onvif_profile?: string; + /** @description Applied only when the effective isOnvif is true; "" clears the stored value (no "unchanged" sentinel). */ + onvif_username?: string; + playback_transport?: components["schemas"]["PlaybackTransport"]; + post_detection_buffer?: components["schemas"]["Int32"]; + pre_detection_buffer?: components["schemas"]["Int32"]; + priority?: components["schemas"]["Int32"]; + protocol?: components["schemas"]["StreamProtocol"]; + ptz_enabled?: boolean; + ptz_has_home?: boolean; + ptz_max_x?: components["schemas"]["Int32"]; + ptz_max_y?: components["schemas"]["Int32"]; + ptz_max_z?: components["schemas"]["Int32"]; + publish_url?: components["schemas"]["PublishUrlList"]; + record?: boolean; + record_audio?: boolean; + record_on_schedule?: boolean; + recording_schedule?: components["schemas"]["HourOfWeekSchedule"]; + retention_days?: components["schemas"]["RetentionDays"]; + /** @description Seconds. */ + segment_duration?: components["schemas"]["Int32"]; + storage_priority?: components["schemas"]["Int32"]; + streaming_enabled?: boolean; + sub_stream_url?: components["schemas"]["NullableClearableString"]; + tags?: components["schemas"]["NullableClearableString"]; + tier_critical_multiplier?: number; + tier_ephemeral_multiplier?: number; + tier_important_multiplier?: number; + /** @description Not validated (an empty string is accepted). A change requires a stream restart. */ + url?: string; + }; + /** @description Summary rows never carry credential-bearing configuration. Presence of any of these keys is a redaction failure regardless of value. */ + SummaryForbiddenSensitiveKeys: unknown; + SummaryPageMeta: { + page: number; + page_size: number; + /** @description Rows visible to the caller after filtering. */ + total: number; + /** @description 0 when total is 0, else ceil(total / page_size). */ + total_pages: number; + }; + /** @enum {string} */ + SummaryStatus: "Running" | "Degraded" | "Error" | "Stopped" | "Unknown"; + /** @description Rejects a URL whose query string carries a non-empty parameter whose key contains pass, pwd, token, secret or apikey (case-insensitive). The C redaction only strips userinfo, so a camera URL like `rtsp://host/live?user=admin&password=x` currently reaches viewers unchanged. This rule is the redaction policy the contract enforces; a server response that violates it is a finding (F-T11-03), not a schema bug. */ + UrlWithoutQuerySecrets: string; + /** @description A URL-like string that carries no userinfo (`user:pass@`) in its authority. Empty string is permitted (viewer redaction falls back to "" when credential stripping fails). A response that contains userinfo here is a credential leak and must fail validation. */ + UrlWithoutUserinfo: string; + /** @description Credential-free URL as delivered to principals without camera.configure. */ + ViewerSafeUrl: components["schemas"]["UrlWithoutUserinfo"] & components["schemas"]["UrlWithoutQuerySecrets"]; + ZonePoint: { + /** @description Normalized 0..1 (not re-validated on read). */ + x: number; + /** @description Normalized 0..1 (not re-validated on read). */ + y: number; + }; + ZonesResponse: { + zones: components["schemas"]["DetectionZone"][]; + }; + }; + responses: { + /** @description Malformed path, query, or body. Message text is operation-specific. */ + BadRequest: { + headers: { + "Cache-Control": components["headers"]["CacheControl"]; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Authenticated but the authorization policy denied the action. Body is `{"error":"Forbidden"}`. */ + Forbidden: { + headers: { + "Cache-Control": components["headers"]["CacheControl"]; + [name: string]: unknown; + }; + content: { + /** + * @example { + * "error": "Forbidden" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Server-side failure (allocation, serialization, database read, or authorization policy evaluation error: `{"error":"Authorization policy evaluation failed"}`). */ + InternalError: { + headers: { + "Cache-Control": components["headers"]["CacheControl"]; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description A size-checked string field exceeded its buffer. */ + PayloadTooLarge: { + headers: { + "Cache-Control": components["headers"]["CacheControl"]; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description `{"error":"Stream not found"}` when no stream with that name is loaded. This check runs BEFORE authentication, so unauthenticated callers can distinguish existing from missing names (finding candidate F-T11-01). `{"error":"Camera not found"}` is returned after authentication when the stream exists but has no fleet inventory row. */ + StreamNotFound: { + headers: { + "Cache-Control": components["headers"]["CacheControl"]; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description No authenticated principal (and demo mode is off). Body is `{"error":"Unauthorized"}`. */ + Unauthorized: { + headers: { + "Cache-Control": components["headers"]["CacheControl"]; + [name: string]: unknown; + }; + content: { + /** + * @example { + * "error": "Unauthorized" + * } + */ + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + parameters: { + /** @description Stream name, percent-encoded as one path segment (the UI uses encodeURIComponent). Routing matches the *encoded* segment against `#` (a run of non-'/' characters), then http_request_extract_path_param decodes it with form semantics: `%XX` is decoded and a literal `+` becomes a space, so a name containing '+' must be sent as `%2B`. The entire encoded suffix after `/api/streams/` must be shorter than 256 bytes, including `/full` or `/zones`. Thus the encoded name limit is 255 bytes for PUT, 250 for `/full`, and 249 for `/zones`. Overflow answers 400 "Invalid request path" ("Invalid stream name" for zones; finding candidate F-T11-09). Names whose decoded form contains '/' (sent as `%2F`) are truncated at the first '/' by the `/full` handler and resolve to a different stream (finding candidate F-T11-08). Path segments that decode to `.` or `..` are rejected by the HTTP parser before routing. */ + StreamNamePath: components["schemas"]["StreamName"]; + }; + requestBodies: never; + headers: { + /** @description Every JSON response sets `no-cache, no-store, must-revalidate` (plus `Pragma: no-cache` and `Expires: 0`). */ + CacheControl: "no-cache, no-store, must-revalidate"; + }; + pathItems: never; +} +export type $defs = Record; +export interface operations { + verifyAuth: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Principal description. */ + 200: { + headers: { + "Cache-Control": components["headers"]["CacheControl"]; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AuthVerifyResponse"]; + }; + }; + 401: components["responses"]["Unauthorized"]; + }; + }; + getClientConfig: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Client configuration. */ + 200: { + headers: { + "Cache-Control": components["headers"]["CacheControl"]; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ClientConfig"]; + }; + }; + 401: components["responses"]["Unauthorized"]; + 500: components["responses"]["InternalError"]; + }; + }; + listDetectionModels: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Built-in models first (motion, api-detection, onvif), then file models. */ + 200: { + headers: { + "Cache-Control": components["headers"]["CacheControl"]; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DetectionModelsResponse"]; + }; + }; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 500: components["responses"]["InternalError"]; + }; + }; + listStreams: { + parameters: { + query?: { + /** @description Invalid: 400 "Invalid availability value". */ + availability?: "all" | "live" | "offline" | "never_connected" | "disabled"; + /** @description Exactly `true` or `1` adds `admin_url` to rows on the admin surface (never on live/lpr). Rows without camera.configure get "". */ + include_admin_url?: string; + /** @description Decimal integer parsed by strtol: leading whitespace and a plus sign are accepted, but trailing whitespace/suffixes are rejected. Empty or out of range: 400 "page and page_size must be positive; page_size max is 100". A page beyond the last returns an empty `streams` array with the real totals. */ + page?: number; + /** @description Out of range: 400 "page and page_size must be positive; page_size max is 100". */ + page_size?: number; + /** @description Case-insensitive substring over name, camera_uuid, tags, location path and inventory address. Values longer than 255 bytes are silently truncated. */ + search?: string; + /** @description Invalid: 400 "Invalid stream summary sort field". Ties break on case-insensitive name. */ + sort_by?: "name" | "status" | "resolution" | "fps" | "recording"; + /** @description Invalid: 400 "sort_order must be asc or desc". */ + sort_order?: "asc" | "desc"; + /** @description Exactly `true` or `1` selects the summary variant; any other value (including `TRUE`) selects the legacy list. */ + summary?: string; + /** @description Invalid value: 400 "surface must be admin, live or lpr". */ + surface?: "admin" | "live" | "lpr"; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Summary page (surface-specific) or legacy array. */ + 200: { + headers: { + "Cache-Control": components["headers"]["CacheControl"]; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["StreamSummaryPageAdmin"] | components["schemas"]["StreamSummaryPageLive"] | components["schemas"]["StreamSummaryPageLpr"] | components["schemas"]["LegacyStreamList"]; + }; + }; + 400: components["responses"]["BadRequest"]; + 401: components["responses"]["Unauthorized"]; + /** @description Includes "Authorization policy evaluation failed", which is also returned for the WHOLE list when any stream lacks a fleet inventory row (finding candidate F-T11-10). */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + updateStream: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Stream name, percent-encoded as one path segment (the UI uses encodeURIComponent). Routing matches the *encoded* segment against `#` (a run of non-'/' characters), then http_request_extract_path_param decodes it with form semantics: `%XX` is decoded and a literal `+` becomes a space, so a name containing '+' must be sent as `%2B`. The entire encoded suffix after `/api/streams/` must be shorter than 256 bytes, including `/full` or `/zones`. Thus the encoded name limit is 255 bytes for PUT, 250 for `/full`, and 249 for `/zones`. Overflow answers 400 "Invalid request path" ("Invalid stream name" for zones; finding candidate F-T11-09). Names whose decoded form contains '/' (sent as `%2F`) are truncated at the first '/' by the `/full` handler and resolve to a different stream (finding candidate F-T11-08). Path segments that decode to `.` or `..` are rejected by the HTTP parser before routing. */ + name: components["parameters"]["StreamNamePath"]; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["StreamUpdateRequest"] | components["schemas"]["StreamPrivacyModeRequest"]; + }; + }; + responses: { + /** @description set_privacy_mode short-circuit result. */ + 200: { + headers: { + "Cache-Control": components["headers"]["CacheControl"]; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["StreamPrivacyModeResponse"]; + }; + }; + /** @description Update accepted; applied asynchronously by a worker thread. */ + 202: { + headers: { + "Cache-Control": components["headers"]["CacheControl"]; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["StreamUpdateAccepted"]; + }; + }; + 400: components["responses"]["BadRequest"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 404: components["responses"]["StreamNotFound"]; + 413: components["responses"]["PayloadTooLarge"]; + 500: components["responses"]["InternalError"]; + }; + }; + getStreamFull: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Stream name, percent-encoded as one path segment (the UI uses encodeURIComponent). Routing matches the *encoded* segment against `#` (a run of non-'/' characters), then http_request_extract_path_param decodes it with form semantics: `%XX` is decoded and a literal `+` becomes a space, so a name containing '+' must be sent as `%2B`. The entire encoded suffix after `/api/streams/` must be shorter than 256 bytes, including `/full` or `/zones`. Thus the encoded name limit is 255 bytes for PUT, 250 for `/full`, and 249 for `/zones`. Overflow answers 400 "Invalid request path" ("Invalid stream name" for zones; finding candidate F-T11-09). Names whose decoded form contains '/' (sent as `%2F`) are truncated at the first '/' by the `/full` handler and resolve to a different stream (finding candidate F-T11-08). Path segments that decode to `.` or `..` are rejected by the HTTP parser before routing. */ + name: components["parameters"]["StreamNamePath"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Stream detail, configure or live-only variant. */ + 200: { + headers: { + "Cache-Control": components["headers"]["CacheControl"]; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["StreamFullResponse"]; + }; + }; + 400: components["responses"]["BadRequest"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 404: components["responses"]["StreamNotFound"]; + 500: components["responses"]["InternalError"]; + }; + }; + getStreamZones: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Stream name, percent-encoded as one path segment (the UI uses encodeURIComponent). Routing matches the *encoded* segment against `#` (a run of non-'/' characters), then http_request_extract_path_param decodes it with form semantics: `%XX` is decoded and a literal `+` becomes a space, so a name containing '+' must be sent as `%2B`. The entire encoded suffix after `/api/streams/` must be shorter than 256 bytes, including `/full` or `/zones`. Thus the encoded name limit is 255 bytes for PUT, 250 for `/full`, and 249 for `/zones`. Overflow answers 400 "Invalid request path" ("Invalid stream name" for zones; finding candidate F-T11-09). Names whose decoded form contains '/' (sent as `%2F`) are truncated at the first '/' by the `/full` handler and resolve to a different stream (finding candidate F-T11-08). Path segments that decode to `.` or `..` are rejected by the HTTP parser before routing. */ + name: components["parameters"]["StreamNamePath"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Zones for the stream (possibly empty). */ + 200: { + headers: { + "Cache-Control": components["headers"]["CacheControl"]; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ZonesResponse"]; + }; + }; + 400: components["responses"]["BadRequest"]; + 401: components["responses"]["Unauthorized"]; + 403: components["responses"]["Forbidden"]; + 404: components["responses"]["StreamNotFound"]; + 500: components["responses"]["InternalError"]; + }; + }; +} diff --git a/web/js/api/generated/validators.d.mts b/web/js/api/generated/validators.d.mts new file mode 100644 index 000000000..6d392364d --- /dev/null +++ b/web/js/api/generated/validators.d.mts @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +import type { components } from './types.js'; + +export interface ValidationIssue { readonly instancePath: string; readonly schemaPath: string; readonly keyword: string; readonly params: Readonly>; readonly message?: string; } +export interface ContractValidator { (data: unknown): data is T; errors?: readonly ValidationIssue[] | null; } + +export const validateAuthRole: ContractValidator; +export const validateAuthVerifyAuthDisabled: ContractValidator; +export const validateAuthVerifyAuthenticated: ContractValidator; +export const validateAuthVerifyDemo: ContractValidator; +export const validateAuthVerifyResponse: ContractValidator; +export const validateAvailability: ContractValidator; +export const validateCameraUuid: ContractValidator; +export const validateClientConfig: ContractValidator; +export const validateDetailStatus: ContractValidator; +export const validateDetectionModel: ContractValidator; +export const validateDetectionModelsResponse: ContractValidator; +export const validateDetectionUrl: ContractValidator; +export const validateDetectionZone: ContractValidator; +export const validateEmptyString: ContractValidator; +export const validateEpochSecondsOrZero: ContractValidator; +export const validateEptzConfigString: ContractValidator; +export const validateEptzConfigV1: ContractValidator; +export const validateErrorResponse: ContractValidator; +export const validateHourOfWeekSchedule: ContractValidator; +export const validateInt32: ContractValidator; +export const validateLegacyStreamList: ContractValidator; +export const validateNullableClearableString: ContractValidator; +export const validatePlaybackTransport: ContractValidator; +export const validatePublishUrlList: ContractValidator; +export const validateRetentionDays: ContractValidator; +export const validateStreamDetail: ContractValidator; +export const validateStreamDetailBase: ContractValidator; +export const validateStreamDetailConfigurable: ContractValidator; +export const validateStreamDetailLiveOnly: ContractValidator; +export const validateStreamFullResponse: ContractValidator; +export const validateStreamFullResponseConfigurable: ContractValidator; +export const validateStreamFullResponseLiveOnly: ContractValidator; +export const validateStreamName: ContractValidator; +export const validateStreamPrivacyModeRequest: ContractValidator; +export const validateStreamPrivacyModeResponse: ContractValidator; +export const validateStreamProtocol: ContractValidator; +export const validateStreamSummaryAdmin: ContractValidator; +export const validateStreamSummaryBase: ContractValidator; +export const validateStreamSummaryLive: ContractValidator; +export const validateStreamSummaryLpr: ContractValidator; +export const validateStreamSummaryPageAdmin: ContractValidator; +export const validateStreamSummaryPageLive: ContractValidator; +export const validateStreamSummaryPageLpr: ContractValidator; +export const validateStreamUpdateAccepted: ContractValidator; +export const validateStreamUpdateRequest: ContractValidator; +export const validateSummaryForbiddenSensitiveKeys: ContractValidator; +export const validateSummaryPageMeta: ContractValidator; +export const validateSummaryStatus: ContractValidator; +export const validateUrlWithoutQuerySecrets: ContractValidator; +export const validateUrlWithoutUserinfo: ContractValidator; +export const validateViewerSafeUrl: ContractValidator; +export const validateZonePoint: ContractValidator; +export const validateZonesResponse: ContractValidator; diff --git a/web/js/api/generated/validators.mjs b/web/js/api/generated/validators.mjs new file mode 100644 index 000000000..89f427211 --- /dev/null +++ b/web/js/api/generated/validators.mjs @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateAuthRole } from './validators/AuthRole.mjs'; +export { validateAuthVerifyAuthDisabled } from './validators/AuthVerifyAuthDisabled.mjs'; +export { validateAuthVerifyAuthenticated } from './validators/AuthVerifyAuthenticated.mjs'; +export { validateAuthVerifyDemo } from './validators/AuthVerifyDemo.mjs'; +export { validateAuthVerifyResponse } from './validators/AuthVerifyResponse.mjs'; +export { validateAvailability } from './validators/Availability.mjs'; +export { validateCameraUuid } from './validators/CameraUuid.mjs'; +export { validateClientConfig } from './validators/ClientConfig.mjs'; +export { validateDetailStatus } from './validators/DetailStatus.mjs'; +export { validateDetectionModel } from './validators/DetectionModel.mjs'; +export { validateDetectionModelsResponse } from './validators/DetectionModelsResponse.mjs'; +export { validateDetectionUrl } from './validators/DetectionUrl.mjs'; +export { validateDetectionZone } from './validators/DetectionZone.mjs'; +export { validateEmptyString } from './validators/EmptyString.mjs'; +export { validateEpochSecondsOrZero } from './validators/EpochSecondsOrZero.mjs'; +export { validateEptzConfigString } from './validators/EptzConfigString.mjs'; +export { validateEptzConfigV1 } from './validators/EptzConfigV1.mjs'; +export { validateErrorResponse } from './validators/ErrorResponse.mjs'; +export { validateHourOfWeekSchedule } from './validators/HourOfWeekSchedule.mjs'; +export { validateInt32 } from './validators/Int32.mjs'; +export { validateLegacyStreamList } from './validators/LegacyStreamList.mjs'; +export { validateNullableClearableString } from './validators/NullableClearableString.mjs'; +export { validatePlaybackTransport } from './validators/PlaybackTransport.mjs'; +export { validatePublishUrlList } from './validators/PublishUrlList.mjs'; +export { validateRetentionDays } from './validators/RetentionDays.mjs'; +export { validateStreamDetail } from './validators/StreamDetail.mjs'; +export { validateStreamDetailBase } from './validators/StreamDetailBase.mjs'; +export { validateStreamDetailConfigurable } from './validators/StreamDetailConfigurable.mjs'; +export { validateStreamDetailLiveOnly } from './validators/StreamDetailLiveOnly.mjs'; +export { validateStreamFullResponse } from './validators/StreamFullResponse.mjs'; +export { validateStreamFullResponseConfigurable } from './validators/StreamFullResponseConfigurable.mjs'; +export { validateStreamFullResponseLiveOnly } from './validators/StreamFullResponseLiveOnly.mjs'; +export { validateStreamName } from './validators/StreamName.mjs'; +export { validateStreamPrivacyModeRequest } from './validators/StreamPrivacyModeRequest.mjs'; +export { validateStreamPrivacyModeResponse } from './validators/StreamPrivacyModeResponse.mjs'; +export { validateStreamProtocol } from './validators/StreamProtocol.mjs'; +export { validateStreamSummaryAdmin } from './validators/StreamSummaryAdmin.mjs'; +export { validateStreamSummaryBase } from './validators/StreamSummaryBase.mjs'; +export { validateStreamSummaryLive } from './validators/StreamSummaryLive.mjs'; +export { validateStreamSummaryLpr } from './validators/StreamSummaryLpr.mjs'; +export { validateStreamSummaryPageAdmin } from './validators/StreamSummaryPageAdmin.mjs'; +export { validateStreamSummaryPageLive } from './validators/StreamSummaryPageLive.mjs'; +export { validateStreamSummaryPageLpr } from './validators/StreamSummaryPageLpr.mjs'; +export { validateStreamUpdateAccepted } from './validators/StreamUpdateAccepted.mjs'; +export { validateStreamUpdateRequest } from './validators/StreamUpdateRequest.mjs'; +export { validateSummaryForbiddenSensitiveKeys } from './validators/SummaryForbiddenSensitiveKeys.mjs'; +export { validateSummaryPageMeta } from './validators/SummaryPageMeta.mjs'; +export { validateSummaryStatus } from './validators/SummaryStatus.mjs'; +export { validateUrlWithoutQuerySecrets } from './validators/UrlWithoutQuerySecrets.mjs'; +export { validateUrlWithoutUserinfo } from './validators/UrlWithoutUserinfo.mjs'; +export { validateViewerSafeUrl } from './validators/ViewerSafeUrl.mjs'; +export { validateZonePoint } from './validators/ZonePoint.mjs'; +export { validateZonesResponse } from './validators/ZonesResponse.mjs'; diff --git a/web/js/api/generated/validators/AuthRole.d.mts b/web/js/api/generated/validators/AuthRole.d.mts new file mode 100644 index 000000000..ce8c5a6fe --- /dev/null +++ b/web/js/api/generated/validators/AuthRole.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateAuthRole } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/AuthRole.mjs b/web/js/api/generated/validators/AuthRole.mjs new file mode 100644 index 000000000..f3a1839a4 --- /dev/null +++ b/web/js/api/generated/validators/AuthRole.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var p=r,o={type:"string",enum:["admin","user","viewer","api","unknown"],$id:"urn:lightnvr:contract:AuthRole"};function r(e,{instancePath:s="",parentData:i,parentDataProperty:u,rootData:l=e,dynamicAnchors:m={}}={}){let t=null,a=0,n=r.evaluated;return n.dynamicProps&&(n.props=void 0),n.dynamicItems&&(n.items=void 0),typeof e!="string"?(r.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1):e==="admin"||e==="user"||e==="viewer"||e==="api"||e==="unknown"?(r.errors=t,a===0):(r.errors=[{instancePath:s,schemaPath:"#/enum",keyword:"enum",params:{allowedValues:o.enum},message:"must be equal to one of the allowed values"}],!1)}r.evaluated={dynamicProps:!1,dynamicItems:!1};export{p as validateAuthRole}; diff --git a/web/js/api/generated/validators/AuthVerifyAuthDisabled.d.mts b/web/js/api/generated/validators/AuthVerifyAuthDisabled.d.mts new file mode 100644 index 000000000..9e93f0588 --- /dev/null +++ b/web/js/api/generated/validators/AuthVerifyAuthDisabled.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateAuthVerifyAuthDisabled } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/AuthVerifyAuthDisabled.mjs b/web/js/api/generated/validators/AuthVerifyAuthDisabled.mjs new file mode 100644 index 000000000..052e0eeba --- /dev/null +++ b/web/js/api/generated/validators/AuthVerifyAuthDisabled.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var c=a;function a(e,{instancePath:u="",parentData:l,parentDataProperty:_,rootData:m=e,dynamicAnchors:h={}}={}){let d=null,s=0,i=a.evaluated;if(i.dynamicProps&&(i.props=void 0),i.dynamicItems&&(i.items=void 0),s===0)if(e&&typeof e=="object"&&!Array.isArray(e)){let o;if(e.authenticated===void 0&&(o="authenticated")||e.username===void 0&&(o="username")||e.role===void 0&&(o="role")||e.must_change_password===void 0&&(o="must_change_password")||e.auth_enabled===void 0&&(o="auth_enabled")||e.auth_timeout_hours===void 0&&(o="auth_timeout_hours")||e.auth_absolute_timeout_hours===void 0&&(o="auth_absolute_timeout_hours")||e.trusted_device_days===void 0&&(o="trusted_device_days"))return a.errors=[{instancePath:u,schemaPath:"#/required",keyword:"required",params:{missingProperty:o},message:"must have required property '"+o+"'"}],!1;if(e.authenticated!==void 0){let t=s;if(e.authenticated!==!0)return a.errors=[{instancePath:u+"/authenticated",schemaPath:"#/properties/authenticated/const",keyword:"const",params:{allowedValue:!0},message:"must be equal to constant"}],!1;var r=t===s}else var r=!0;if(r){if(e.username!==void 0){let t=s;if(e.username!=="admin")return a.errors=[{instancePath:u+"/username",schemaPath:"#/properties/username/const",keyword:"const",params:{allowedValue:"admin"},message:"must be equal to constant"}],!1;var r=t===s}else var r=!0;if(r){if(e.role!==void 0){let t=s;if(e.role!=="admin")return a.errors=[{instancePath:u+"/role",schemaPath:"#/properties/role/const",keyword:"const",params:{allowedValue:"admin"},message:"must be equal to constant"}],!1;var r=t===s}else var r=!0;if(r){if(e.must_change_password!==void 0){let t=s;if(e.must_change_password!==!1)return a.errors=[{instancePath:u+"/must_change_password",schemaPath:"#/properties/must_change_password/const",keyword:"const",params:{allowedValue:!1},message:"must be equal to constant"}],!1;var r=t===s}else var r=!0;if(r){if(e.auth_enabled!==void 0){let t=s;if(e.auth_enabled!==!1)return a.errors=[{instancePath:u+"/auth_enabled",schemaPath:"#/properties/auth_enabled/const",keyword:"const",params:{allowedValue:!1},message:"must be equal to constant"}],!1;var r=t===s}else var r=!0;if(r){if(e.auth_timeout_hours!==void 0){let t=e.auth_timeout_hours,n=s;if(!(typeof t=="number"&&!(t%1)&&!isNaN(t)&&isFinite(t)))return a.errors=[{instancePath:u+"/auth_timeout_hours",schemaPath:"#/properties/auth_timeout_hours/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;var r=n===s}else var r=!0;if(r){if(e.auth_absolute_timeout_hours!==void 0){let t=e.auth_absolute_timeout_hours,n=s;if(!(typeof t=="number"&&!(t%1)&&!isNaN(t)&&isFinite(t)))return a.errors=[{instancePath:u+"/auth_absolute_timeout_hours",schemaPath:"#/properties/auth_absolute_timeout_hours/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;var r=n===s}else var r=!0;if(r)if(e.trusted_device_days!==void 0){let t=e.trusted_device_days,n=s;if(!(typeof t=="number"&&!(t%1)&&!isNaN(t)&&isFinite(t)))return a.errors=[{instancePath:u+"/trusted_device_days",schemaPath:"#/properties/trusted_device_days/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;var r=n===s}else var r=!0}}}}}}}else return a.errors=[{instancePath:u,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return a.errors=d,s===0}a.evaluated={props:{authenticated:!0,username:!0,role:!0,must_change_password:!0,auth_enabled:!0,auth_timeout_hours:!0,auth_absolute_timeout_hours:!0,trusted_device_days:!0},dynamicProps:!1,dynamicItems:!1};export{c as validateAuthVerifyAuthDisabled}; diff --git a/web/js/api/generated/validators/AuthVerifyAuthenticated.d.mts b/web/js/api/generated/validators/AuthVerifyAuthenticated.d.mts new file mode 100644 index 000000000..a173ff6cd --- /dev/null +++ b/web/js/api/generated/validators/AuthVerifyAuthenticated.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateAuthVerifyAuthenticated } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/AuthVerifyAuthenticated.mjs b/web/js/api/generated/validators/AuthVerifyAuthenticated.mjs new file mode 100644 index 000000000..f9d16368b --- /dev/null +++ b/web/js/api/generated/validators/AuthVerifyAuthenticated.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var v=(e,a)=>()=>{try{return a||e((a={exports:{}}).exports,a),a.exports}catch(c){throw a=0,c}};var y=v(_=>{"use strict";Object.defineProperty(_,"__esModule",{value:!0});function f(e){let a=e.length,c=0,d=0,l;for(;d=55296&&l<=56319&&d63)return i.errors=[{instancePath:a+"/username",schemaPath:"#/properties/username/maxLength",keyword:"maxLength",params:{limit:63},message:"must NOT have more than 63 characters"}],!1}else return i.errors=[{instancePath:a+"/username",schemaPath:"#/properties/username/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var t=u===s}else var t=!0;if(t){if(e.email!==void 0){let r=e.email,u=s;if(s===u)if(typeof r=="string"){if(g(r)>127)return i.errors=[{instancePath:a+"/email",schemaPath:"#/properties/email/maxLength",keyword:"maxLength",params:{limit:127},message:"must NOT have more than 127 characters"}],!1}else return i.errors=[{instancePath:a+"/email",schemaPath:"#/properties/email/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var t=u===s}else var t=!0;if(t){if(e.role!==void 0){let r=s;m(e.role,{instancePath:a+"/role",parentData:e,parentDataProperty:"role",rootData:l,dynamicAnchors:h})||(p=p===null?m.errors:p.concat(m.errors),s=p.length);var t=r===s}else var t=!0;if(t){if(e.role_id!==void 0){let r=e.role_id,u=s;if(!(typeof r=="number"&&!(r%1)&&!isNaN(r)&&isFinite(r)))return i.errors=[{instancePath:a+"/role_id",schemaPath:"#/properties/role_id/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;if(s===u&&typeof r=="number"&&isFinite(r)){if(r>3||isNaN(r))return i.errors=[{instancePath:a+"/role_id",schemaPath:"#/properties/role_id/maximum",keyword:"maximum",params:{comparison:"<=",limit:3},message:"must be <= 3"}],!1;if(r<0||isNaN(r))return i.errors=[{instancePath:a+"/role_id",schemaPath:"#/properties/role_id/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1}var t=u===s}else var t=!0;if(t){if(e.is_active!==void 0){let r=s;if(typeof e.is_active!="boolean")return i.errors=[{instancePath:a+"/is_active",schemaPath:"#/properties/is_active/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var t=r===s}else var t=!0;if(t){if(e.password_change_locked!==void 0){let r=s;if(typeof e.password_change_locked!="boolean")return i.errors=[{instancePath:a+"/password_change_locked",schemaPath:"#/properties/password_change_locked/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var t=r===s}else var t=!0;if(t){if(e.must_change_password!==void 0){let r=s;if(typeof e.must_change_password!="boolean")return i.errors=[{instancePath:a+"/must_change_password",schemaPath:"#/properties/must_change_password/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var t=r===s}else var t=!0;if(t){if(e.auth_enabled!==void 0){let r=s;if(e.auth_enabled!==!0)return i.errors=[{instancePath:a+"/auth_enabled",schemaPath:"#/properties/auth_enabled/const",keyword:"const",params:{allowedValue:!0},message:"must be equal to constant"}],!1;var t=r===s}else var t=!0;if(t){if(e.auth_timeout_hours!==void 0){let r=e.auth_timeout_hours,u=s;if(!(typeof r=="number"&&!(r%1)&&!isNaN(r)&&isFinite(r)))return i.errors=[{instancePath:a+"/auth_timeout_hours",schemaPath:"#/properties/auth_timeout_hours/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;var t=u===s}else var t=!0;if(t){if(e.auth_absolute_timeout_hours!==void 0){let r=e.auth_absolute_timeout_hours,u=s;if(!(typeof r=="number"&&!(r%1)&&!isNaN(r)&&isFinite(r)))return i.errors=[{instancePath:a+"/auth_absolute_timeout_hours",schemaPath:"#/properties/auth_absolute_timeout_hours/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;var t=u===s}else var t=!0;if(t)if(e.trusted_device_days!==void 0){let r=e.trusted_device_days,u=s;if(!(typeof r=="number"&&!(r%1)&&!isNaN(r)&&isFinite(r)))return i.errors=[{instancePath:a+"/trusted_device_days",schemaPath:"#/properties/trusted_device_days/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;var t=u===s}else var t=!0}}}}}}}}}}}}else return i.errors=[{instancePath:a,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return i.errors=p,s===0}i.evaluated={props:{authenticated:!0,id:!0,username:!0,email:!0,role:!0,role_id:!0,is_active:!0,password_change_locked:!0,must_change_password:!0,auth_enabled:!0,auth_timeout_hours:!0,auth_absolute_timeout_hours:!0,trusted_device_days:!0},dynamicProps:!1,dynamicItems:!1};export{P as validateAuthVerifyAuthenticated}; diff --git a/web/js/api/generated/validators/AuthVerifyDemo.d.mts b/web/js/api/generated/validators/AuthVerifyDemo.d.mts new file mode 100644 index 000000000..5c711d0f3 --- /dev/null +++ b/web/js/api/generated/validators/AuthVerifyDemo.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateAuthVerifyDemo } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/AuthVerifyDemo.mjs b/web/js/api/generated/validators/AuthVerifyDemo.mjs new file mode 100644 index 000000000..19c2e30d1 --- /dev/null +++ b/web/js/api/generated/validators/AuthVerifyDemo.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var f=t;function t(e,{instancePath:o="",parentData:d,parentDataProperty:i,rootData:l=e,dynamicAnchors:c={}}={}){let m=null,s=0,u=t.evaluated;if(u.dynamicProps&&(u.props=void 0),u.dynamicItems&&(u.items=void 0),s===0)if(e&&typeof e=="object"&&!Array.isArray(e)){let n;if(e.authenticated===void 0&&(n="authenticated")||e.demo_mode===void 0&&(n="demo_mode")||e.username===void 0&&(n="username")||e.role===void 0&&(n="role")||e.must_change_password===void 0&&(n="must_change_password"))return t.errors=[{instancePath:o,schemaPath:"#/required",keyword:"required",params:{missingProperty:n},message:"must have required property '"+n+"'"}],!1;if(e.authenticated!==void 0){let a=s;if(e.authenticated!==!1)return t.errors=[{instancePath:o+"/authenticated",schemaPath:"#/properties/authenticated/const",keyword:"const",params:{allowedValue:!1},message:"must be equal to constant"}],!1;var r=a===s}else var r=!0;if(r){if(e.demo_mode!==void 0){let a=s;if(e.demo_mode!==!0)return t.errors=[{instancePath:o+"/demo_mode",schemaPath:"#/properties/demo_mode/const",keyword:"const",params:{allowedValue:!0},message:"must be equal to constant"}],!1;var r=a===s}else var r=!0;if(r){if(e.username!==void 0){let a=s;if(e.username!=="demo")return t.errors=[{instancePath:o+"/username",schemaPath:"#/properties/username/const",keyword:"const",params:{allowedValue:"demo"},message:"must be equal to constant"}],!1;var r=a===s}else var r=!0;if(r){if(e.role!==void 0){let a=s;if(e.role!=="viewer")return t.errors=[{instancePath:o+"/role",schemaPath:"#/properties/role/const",keyword:"const",params:{allowedValue:"viewer"},message:"must be equal to constant"}],!1;var r=a===s}else var r=!0;if(r)if(e.must_change_password!==void 0){let a=s;if(e.must_change_password!==!1)return t.errors=[{instancePath:o+"/must_change_password",schemaPath:"#/properties/must_change_password/const",keyword:"const",params:{allowedValue:!1},message:"must be equal to constant"}],!1;var r=a===s}else var r=!0}}}}else return t.errors=[{instancePath:o,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return t.errors=m,s===0}t.evaluated={props:{authenticated:!0,demo_mode:!0,username:!0,role:!0,must_change_password:!0},dynamicProps:!1,dynamicItems:!1};export{f as validateAuthVerifyDemo}; diff --git a/web/js/api/generated/validators/AuthVerifyResponse.d.mts b/web/js/api/generated/validators/AuthVerifyResponse.d.mts new file mode 100644 index 000000000..8c848b818 --- /dev/null +++ b/web/js/api/generated/validators/AuthVerifyResponse.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateAuthVerifyResponse } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/AuthVerifyResponse.mjs b/web/js/api/generated/validators/AuthVerifyResponse.mjs new file mode 100644 index 000000000..64f38b75b --- /dev/null +++ b/web/js/api/generated/validators/AuthVerifyResponse.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var N=(e,a)=>()=>{try{return a||e((a={exports:{}}).exports,a),a.exports}catch(f){throw a=0,f}};var P=N(w=>{"use strict";Object.defineProperty(w,"__esModule",{value:!0});function k(e){let a=e.length,f=0,_=0,c;for(;_=55296&&c<=56319&&_63)return d.errors=[{instancePath:a+"/username",schemaPath:"#/properties/username/maxLength",keyword:"maxLength",params:{limit:63},message:"must NOT have more than 63 characters"}],!1}else return d.errors=[{instancePath:a+"/username",schemaPath:"#/properties/username/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var s=l===t}else var s=!0;if(s){if(e.email!==void 0){let r=e.email,l=t;if(t===l)if(typeof r=="string"){if(q(r)>127)return d.errors=[{instancePath:a+"/email",schemaPath:"#/properties/email/maxLength",keyword:"maxLength",params:{limit:127},message:"must NOT have more than 127 characters"}],!1}else return d.errors=[{instancePath:a+"/email",schemaPath:"#/properties/email/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var s=l===t}else var s=!0;if(s){if(e.role!==void 0){let r=t;y(e.role,{instancePath:a+"/role",parentData:e,parentDataProperty:"role",rootData:c,dynamicAnchors:p})||(n=n===null?y.errors:n.concat(y.errors),t=n.length);var s=r===t}else var s=!0;if(s){if(e.role_id!==void 0){let r=e.role_id,l=t;if(!(typeof r=="number"&&!(r%1)&&!isNaN(r)&&isFinite(r)))return d.errors=[{instancePath:a+"/role_id",schemaPath:"#/properties/role_id/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;if(t===l&&typeof r=="number"&&isFinite(r)){if(r>3||isNaN(r))return d.errors=[{instancePath:a+"/role_id",schemaPath:"#/properties/role_id/maximum",keyword:"maximum",params:{comparison:"<=",limit:3},message:"must be <= 3"}],!1;if(r<0||isNaN(r))return d.errors=[{instancePath:a+"/role_id",schemaPath:"#/properties/role_id/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1}var s=l===t}else var s=!0;if(s){if(e.is_active!==void 0){let r=t;if(typeof e.is_active!="boolean")return d.errors=[{instancePath:a+"/is_active",schemaPath:"#/properties/is_active/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var s=r===t}else var s=!0;if(s){if(e.password_change_locked!==void 0){let r=t;if(typeof e.password_change_locked!="boolean")return d.errors=[{instancePath:a+"/password_change_locked",schemaPath:"#/properties/password_change_locked/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var s=r===t}else var s=!0;if(s){if(e.must_change_password!==void 0){let r=t;if(typeof e.must_change_password!="boolean")return d.errors=[{instancePath:a+"/must_change_password",schemaPath:"#/properties/must_change_password/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var s=r===t}else var s=!0;if(s){if(e.auth_enabled!==void 0){let r=t;if(e.auth_enabled!==!0)return d.errors=[{instancePath:a+"/auth_enabled",schemaPath:"#/properties/auth_enabled/const",keyword:"const",params:{allowedValue:!0},message:"must be equal to constant"}],!1;var s=r===t}else var s=!0;if(s){if(e.auth_timeout_hours!==void 0){let r=e.auth_timeout_hours,l=t;if(!(typeof r=="number"&&!(r%1)&&!isNaN(r)&&isFinite(r)))return d.errors=[{instancePath:a+"/auth_timeout_hours",schemaPath:"#/properties/auth_timeout_hours/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;var s=l===t}else var s=!0;if(s){if(e.auth_absolute_timeout_hours!==void 0){let r=e.auth_absolute_timeout_hours,l=t;if(!(typeof r=="number"&&!(r%1)&&!isNaN(r)&&isFinite(r)))return d.errors=[{instancePath:a+"/auth_absolute_timeout_hours",schemaPath:"#/properties/auth_absolute_timeout_hours/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;var s=l===t}else var s=!0;if(s)if(e.trusted_device_days!==void 0){let r=e.trusted_device_days,l=t;if(!(typeof r=="number"&&!(r%1)&&!isNaN(r)&&isFinite(r)))return d.errors=[{instancePath:a+"/trusted_device_days",schemaPath:"#/properties/trusted_device_days/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;var s=l===t}else var s=!0}}}}}}}}}}}}else return d.errors=[{instancePath:a,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return d.errors=n,t===0}d.evaluated={props:{authenticated:!0,id:!0,username:!0,email:!0,role:!0,role_id:!0,is_active:!0,password_change_locked:!0,must_change_password:!0,auth_enabled:!0,auth_timeout_hours:!0,auth_absolute_timeout_hours:!0,trusted_device_days:!0},dynamicProps:!1,dynamicItems:!1};function m(e,{instancePath:a="",parentData:f,parentDataProperty:_,rootData:c=e,dynamicAnchors:p={}}={}){let n=null,t=0,o=m.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),t===0)if(e&&typeof e=="object"&&!Array.isArray(e)){let u;if(e.authenticated===void 0&&(u="authenticated")||e.username===void 0&&(u="username")||e.role===void 0&&(u="role")||e.must_change_password===void 0&&(u="must_change_password")||e.auth_enabled===void 0&&(u="auth_enabled")||e.auth_timeout_hours===void 0&&(u="auth_timeout_hours")||e.auth_absolute_timeout_hours===void 0&&(u="auth_absolute_timeout_hours")||e.trusted_device_days===void 0&&(u="trusted_device_days"))return m.errors=[{instancePath:a,schemaPath:"#/required",keyword:"required",params:{missingProperty:u},message:"must have required property '"+u+"'"}],!1;if(e.authenticated!==void 0){let r=t;if(e.authenticated!==!0)return m.errors=[{instancePath:a+"/authenticated",schemaPath:"#/properties/authenticated/const",keyword:"const",params:{allowedValue:!0},message:"must be equal to constant"}],!1;var s=r===t}else var s=!0;if(s){if(e.username!==void 0){let r=t;if(e.username!=="admin")return m.errors=[{instancePath:a+"/username",schemaPath:"#/properties/username/const",keyword:"const",params:{allowedValue:"admin"},message:"must be equal to constant"}],!1;var s=r===t}else var s=!0;if(s){if(e.role!==void 0){let r=t;if(e.role!=="admin")return m.errors=[{instancePath:a+"/role",schemaPath:"#/properties/role/const",keyword:"const",params:{allowedValue:"admin"},message:"must be equal to constant"}],!1;var s=r===t}else var s=!0;if(s){if(e.must_change_password!==void 0){let r=t;if(e.must_change_password!==!1)return m.errors=[{instancePath:a+"/must_change_password",schemaPath:"#/properties/must_change_password/const",keyword:"const",params:{allowedValue:!1},message:"must be equal to constant"}],!1;var s=r===t}else var s=!0;if(s){if(e.auth_enabled!==void 0){let r=t;if(e.auth_enabled!==!1)return m.errors=[{instancePath:a+"/auth_enabled",schemaPath:"#/properties/auth_enabled/const",keyword:"const",params:{allowedValue:!1},message:"must be equal to constant"}],!1;var s=r===t}else var s=!0;if(s){if(e.auth_timeout_hours!==void 0){let r=e.auth_timeout_hours,l=t;if(!(typeof r=="number"&&!(r%1)&&!isNaN(r)&&isFinite(r)))return m.errors=[{instancePath:a+"/auth_timeout_hours",schemaPath:"#/properties/auth_timeout_hours/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;var s=l===t}else var s=!0;if(s){if(e.auth_absolute_timeout_hours!==void 0){let r=e.auth_absolute_timeout_hours,l=t;if(!(typeof r=="number"&&!(r%1)&&!isNaN(r)&&isFinite(r)))return m.errors=[{instancePath:a+"/auth_absolute_timeout_hours",schemaPath:"#/properties/auth_absolute_timeout_hours/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;var s=l===t}else var s=!0;if(s)if(e.trusted_device_days!==void 0){let r=e.trusted_device_days,l=t;if(!(typeof r=="number"&&!(r%1)&&!isNaN(r)&&isFinite(r)))return m.errors=[{instancePath:a+"/trusted_device_days",schemaPath:"#/properties/trusted_device_days/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;var s=l===t}else var s=!0}}}}}}}else return m.errors=[{instancePath:a,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return m.errors=n,t===0}m.evaluated={props:{authenticated:!0,username:!0,role:!0,must_change_password:!0,auth_enabled:!0,auth_timeout_hours:!0,auth_absolute_timeout_hours:!0,trusted_device_days:!0},dynamicProps:!1,dynamicItems:!1};function h(e,{instancePath:a="",parentData:f,parentDataProperty:_,rootData:c=e,dynamicAnchors:p={}}={}){let n=null,t=0,o=h.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),t===0)if(e&&typeof e=="object"&&!Array.isArray(e)){let u;if(e.authenticated===void 0&&(u="authenticated")||e.demo_mode===void 0&&(u="demo_mode")||e.username===void 0&&(u="username")||e.role===void 0&&(u="role")||e.must_change_password===void 0&&(u="must_change_password"))return h.errors=[{instancePath:a,schemaPath:"#/required",keyword:"required",params:{missingProperty:u},message:"must have required property '"+u+"'"}],!1;if(e.authenticated!==void 0){let r=t;if(e.authenticated!==!1)return h.errors=[{instancePath:a+"/authenticated",schemaPath:"#/properties/authenticated/const",keyword:"const",params:{allowedValue:!1},message:"must be equal to constant"}],!1;var s=r===t}else var s=!0;if(s){if(e.demo_mode!==void 0){let r=t;if(e.demo_mode!==!0)return h.errors=[{instancePath:a+"/demo_mode",schemaPath:"#/properties/demo_mode/const",keyword:"const",params:{allowedValue:!0},message:"must be equal to constant"}],!1;var s=r===t}else var s=!0;if(s){if(e.username!==void 0){let r=t;if(e.username!=="demo")return h.errors=[{instancePath:a+"/username",schemaPath:"#/properties/username/const",keyword:"const",params:{allowedValue:"demo"},message:"must be equal to constant"}],!1;var s=r===t}else var s=!0;if(s){if(e.role!==void 0){let r=t;if(e.role!=="viewer")return h.errors=[{instancePath:a+"/role",schemaPath:"#/properties/role/const",keyword:"const",params:{allowedValue:"viewer"},message:"must be equal to constant"}],!1;var s=r===t}else var s=!0;if(s)if(e.must_change_password!==void 0){let r=t;if(e.must_change_password!==!1)return h.errors=[{instancePath:a+"/must_change_password",schemaPath:"#/properties/must_change_password/const",keyword:"const",params:{allowedValue:!1},message:"must be equal to constant"}],!1;var s=r===t}else var s=!0}}}}else return h.errors=[{instancePath:a,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return h.errors=n,t===0}h.evaluated={props:{authenticated:!0,demo_mode:!0,username:!0,role:!0,must_change_password:!0},dynamicProps:!1,dynamicItems:!1};function g(e,{instancePath:a="",parentData:f,parentDataProperty:_,rootData:c=e,dynamicAnchors:p={}}={}){let n=null,t=0,o=g.evaluated;o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0);let s=t,u=!1,r=null,l=t;d(e,{instancePath:a,parentData:f,parentDataProperty:_,rootData:c,dynamicAnchors:p})||(n=n===null?d.errors:n.concat(d.errors),t=n.length);var v=l===t;if(v){u=!0,r=0;var i={};i.authenticated=!0,i.id=!0,i.username=!0,i.email=!0,i.role=!0,i.role_id=!0,i.is_active=!0,i.password_change_locked=!0,i.must_change_password=!0,i.auth_enabled=!0,i.auth_timeout_hours=!0,i.auth_absolute_timeout_hours=!0,i.trusted_device_days=!0}let A=t;m(e,{instancePath:a,parentData:f,parentDataProperty:_,rootData:c,dynamicAnchors:p})||(n=n===null?m.errors:n.concat(m.errors),t=n.length);var v=A===t;if(v&&u)u=!1,r=[r,1];else{v&&(u=!0,r=1,i!==!0&&(i=i||{},i.authenticated=!0,i.username=!0,i.role=!0,i.must_change_password=!0,i.auth_enabled=!0,i.auth_timeout_hours=!0,i.auth_absolute_timeout_hours=!0,i.trusted_device_days=!0));let b=t;h(e,{instancePath:a,parentData:f,parentDataProperty:_,rootData:c,dynamicAnchors:p})||(n=n===null?h.errors:n.concat(h.errors),t=n.length);var v=b===t;v&&u?(u=!1,r=[r,2]):v&&(u=!0,r=2,i!==!0&&(i=i||{},i.authenticated=!0,i.demo_mode=!0,i.username=!0,i.role=!0,i.must_change_password=!0))}if(u)t=s,n!==null&&(s?n.length=s:n=null);else{let b={instancePath:a,schemaPath:"#/oneOf",keyword:"oneOf",params:{passingSchemas:r},message:"must match exactly one schema in oneOf"};return n===null?n=[b]:n.push(b),t++,g.errors=n,!1}return g.errors=n,o.props=i,t===0}g.evaluated={dynamicProps:!0,dynamicItems:!1};export{D as validateAuthVerifyResponse}; diff --git a/web/js/api/generated/validators/Availability.d.mts b/web/js/api/generated/validators/Availability.d.mts new file mode 100644 index 000000000..65c9920d7 --- /dev/null +++ b/web/js/api/generated/validators/Availability.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateAvailability } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/Availability.mjs b/web/js/api/generated/validators/Availability.mjs new file mode 100644 index 000000000..80c1dfdb7 --- /dev/null +++ b/web/js/api/generated/validators/Availability.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var d=r,i={type:"string",enum:["live","offline","never_connected","disabled"],$id:"urn:lightnvr:contract:Availability"};function r(e,{instancePath:n="",parentData:l,parentDataProperty:o,rootData:m=e,dynamicAnchors:u={}}={}){let s=null,a=0,t=r.evaluated;return t.dynamicProps&&(t.props=void 0),t.dynamicItems&&(t.items=void 0),typeof e!="string"?(r.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1):e==="live"||e==="offline"||e==="never_connected"||e==="disabled"?(r.errors=s,a===0):(r.errors=[{instancePath:n,schemaPath:"#/enum",keyword:"enum",params:{allowedValues:i.enum},message:"must be equal to one of the allowed values"}],!1)}r.evaluated={dynamicProps:!1,dynamicItems:!1};export{d as validateAvailability}; diff --git a/web/js/api/generated/validators/CameraUuid.d.mts b/web/js/api/generated/validators/CameraUuid.d.mts new file mode 100644 index 000000000..30f9d7b79 --- /dev/null +++ b/web/js/api/generated/validators/CameraUuid.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateCameraUuid } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/CameraUuid.mjs b/web/js/api/generated/validators/CameraUuid.mjs new file mode 100644 index 000000000..383542609 --- /dev/null +++ b/web/js/api/generated/validators/CameraUuid.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var m=(t,e)=>()=>{try{return e||t((e={exports:{}}).exports,e),e.exports}catch(n){throw e=0,n}};var l=m(i=>{"use strict";Object.defineProperty(i,"__esModule",{value:!0});function o(t){let e=t.length,n=0,r=0,s;for(;r=55296&&s<=56319&&r36)return a.errors=[{instancePath:e,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:36},message:"must NOT have more than 36 characters"}],!1;if(!p.test(t))return a.errors=[{instancePath:e,schemaPath:"#/pattern",keyword:"pattern",params:{pattern:"^(|[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})$"},message:'must match pattern "^(|[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})$"'}],!1}else return a.errors=[{instancePath:e,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return a.errors=c,u===0}a.evaluated={dynamicProps:!1,dynamicItems:!1};export{g as validateCameraUuid}; diff --git a/web/js/api/generated/validators/ClientConfig.d.mts b/web/js/api/generated/validators/ClientConfig.d.mts new file mode 100644 index 000000000..2ec31c6cc --- /dev/null +++ b/web/js/api/generated/validators/ClientConfig.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateClientConfig } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/ClientConfig.mjs b/web/js/api/generated/validators/ClientConfig.mjs new file mode 100644 index 000000000..e0ff6c0e4 --- /dev/null +++ b/web/js/api/generated/validators/ClientConfig.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var f=i;function l(e,{instancePath:s="",parentData:p,parentDataProperty:b,rootData:c=e,dynamicAnchors:d={}}={}){let a=null,t=0,_=l.evaluated;if(_.dynamicProps&&(_.props=void 0),_.dynamicItems&&(_.items=void 0),!(typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)))return l.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;if(t===0&&typeof e=="number"&&isFinite(e)){if(e>2147483647||isNaN(e))return l.errors=[{instancePath:s,schemaPath:"#/maximum",keyword:"maximum",params:{comparison:"<=",limit:2147483647},message:"must be <= 2147483647"}],!1;if(e<-2147483648||isNaN(e))return l.errors=[{instancePath:s,schemaPath:"#/minimum",keyword:"minimum",params:{comparison:">=",limit:-2147483648},message:"must be >= -2147483648"}],!1}return l.errors=a,t===0}l.evaluated={dynamicProps:!1,dynamicItems:!1};var m={type:"string",enum:["auto","webrtc_only","mse_only","hls_only","webrtc_then_mse","mse_then_hls"],$id:"urn:lightnvr:contract:PlaybackTransport"};function u(e,{instancePath:s="",parentData:p,parentDataProperty:b,rootData:c=e,dynamicAnchors:d={}}={}){let a=null,t=0,_=u.evaluated;return _.dynamicProps&&(_.props=void 0),_.dynamicItems&&(_.items=void 0),typeof e!="string"?(u.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1):e==="auto"||e==="webrtc_only"||e==="mse_only"||e==="hls_only"||e==="webrtc_then_mse"||e==="mse_then_hls"?(u.errors=a,t===0):(u.errors=[{instancePath:s,schemaPath:"#/enum",keyword:"enum",params:{allowedValues:m.enum},message:"must be equal to one of the allowed values"}],!1)}u.evaluated={dynamicProps:!1,dynamicItems:!1};function i(e,{instancePath:s="",parentData:p,parentDataProperty:b,rootData:c=e,dynamicAnchors:d={}}={}){let a=null,t=0,_=i.evaluated;if(_.dynamicProps&&(_.props=void 0),_.dynamicItems&&(_.items=void 0),t===0)if(e&&typeof e=="object"&&!Array.isArray(e)){let n;if(e.go2rtc_enabled===void 0&&(n="go2rtc_enabled")||e.web_auth_enabled===void 0&&(n="web_auth_enabled")||e.demo_mode===void 0&&(n="demo_mode")||e.audio_disabled===void 0&&(n="audio_disabled")||e.auto_disabled===void 0&&(n="auto_disabled")||e.go2rtc_available===void 0&&(n="go2rtc_available")||e.go2rtc_api_port===void 0&&(n="go2rtc_api_port")||e.go2rtc_base_path===void 0&&(n="go2rtc_base_path")||e.webrtc_disabled===void 0&&(n="webrtc_disabled")||e.hls_disabled===void 0&&(n="hls_disabled")||e.mse_disabled===void 0&&(n="mse_disabled")||e.default_playback_transport===void 0&&(n="default_playback_transport")||e.go2rtc_force_native_hls===void 0&&(n="go2rtc_force_native_hls")||e.webrtc_connection_timeout_ms===void 0&&(n="webrtc_connection_timeout_ms")||e.webrtc_ice_recovery_timeout_ms===void 0&&(n="webrtc_ice_recovery_timeout_ms")||e.generate_thumbnails===void 0&&(n="generate_thumbnails")||e.thumbnails_per_recording===void 0&&(n="thumbnails_per_recording"))return i.errors=[{instancePath:s,schemaPath:"#/required",keyword:"required",params:{missingProperty:n},message:"must have required property '"+n+"'"}],!1;if(e.go2rtc_enabled!==void 0){let o=t;if(typeof e.go2rtc_enabled!="boolean")return i.errors=[{instancePath:s+"/go2rtc_enabled",schemaPath:"#/properties/go2rtc_enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var r=o===t}else var r=!0;if(r){if(e.web_auth_enabled!==void 0){let o=t;if(typeof e.web_auth_enabled!="boolean")return i.errors=[{instancePath:s+"/web_auth_enabled",schemaPath:"#/properties/web_auth_enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var r=o===t}else var r=!0;if(r){if(e.demo_mode!==void 0){let o=t;if(typeof e.demo_mode!="boolean")return i.errors=[{instancePath:s+"/demo_mode",schemaPath:"#/properties/demo_mode/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var r=o===t}else var r=!0;if(r){if(e.audio_disabled!==void 0){let o=t;if(typeof e.audio_disabled!="boolean")return i.errors=[{instancePath:s+"/audio_disabled",schemaPath:"#/properties/audio_disabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var r=o===t}else var r=!0;if(r){if(e.auto_disabled!==void 0){let o=t;if(typeof e.auto_disabled!="boolean")return i.errors=[{instancePath:s+"/auto_disabled",schemaPath:"#/properties/auto_disabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var r=o===t}else var r=!0;if(r){if(e.go2rtc_available!==void 0){let o=t;if(typeof e.go2rtc_available!="boolean")return i.errors=[{instancePath:s+"/go2rtc_available",schemaPath:"#/properties/go2rtc_available/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var r=o===t}else var r=!0;if(r){if(e.go2rtc_api_port!==void 0){let o=t;l(e.go2rtc_api_port,{instancePath:s+"/go2rtc_api_port",parentData:e,parentDataProperty:"go2rtc_api_port",rootData:c,dynamicAnchors:d})||(a=a===null?l.errors:a.concat(l.errors),t=a.length);var r=o===t}else var r=!0;if(r){if(e.go2rtc_base_path!==void 0){let o=t;if(typeof e.go2rtc_base_path!="string")return i.errors=[{instancePath:s+"/go2rtc_base_path",schemaPath:"#/properties/go2rtc_base_path/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var r=o===t}else var r=!0;if(r){if(e.webrtc_disabled!==void 0){let o=t;if(typeof e.webrtc_disabled!="boolean")return i.errors=[{instancePath:s+"/webrtc_disabled",schemaPath:"#/properties/webrtc_disabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var r=o===t}else var r=!0;if(r){if(e.hls_disabled!==void 0){let o=t;if(typeof e.hls_disabled!="boolean")return i.errors=[{instancePath:s+"/hls_disabled",schemaPath:"#/properties/hls_disabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var r=o===t}else var r=!0;if(r){if(e.mse_disabled!==void 0){let o=t;if(typeof e.mse_disabled!="boolean")return i.errors=[{instancePath:s+"/mse_disabled",schemaPath:"#/properties/mse_disabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var r=o===t}else var r=!0;if(r){if(e.default_playback_transport!==void 0){let o=t;u(e.default_playback_transport,{instancePath:s+"/default_playback_transport",parentData:e,parentDataProperty:"default_playback_transport",rootData:c,dynamicAnchors:d})||(a=a===null?u.errors:a.concat(u.errors),t=a.length);var r=o===t}else var r=!0;if(r){if(e.go2rtc_force_native_hls!==void 0){let o=t;if(typeof e.go2rtc_force_native_hls!="boolean")return i.errors=[{instancePath:s+"/go2rtc_force_native_hls",schemaPath:"#/properties/go2rtc_force_native_hls/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var r=o===t}else var r=!0;if(r){if(e.webrtc_connection_timeout_ms!==void 0){let o=t;l(e.webrtc_connection_timeout_ms,{instancePath:s+"/webrtc_connection_timeout_ms",parentData:e,parentDataProperty:"webrtc_connection_timeout_ms",rootData:c,dynamicAnchors:d})||(a=a===null?l.errors:a.concat(l.errors),t=a.length);var r=o===t}else var r=!0;if(r){if(e.webrtc_ice_recovery_timeout_ms!==void 0){let o=t;l(e.webrtc_ice_recovery_timeout_ms,{instancePath:s+"/webrtc_ice_recovery_timeout_ms",parentData:e,parentDataProperty:"webrtc_ice_recovery_timeout_ms",rootData:c,dynamicAnchors:d})||(a=a===null?l.errors:a.concat(l.errors),t=a.length);var r=o===t}else var r=!0;if(r){if(e.generate_thumbnails!==void 0){let o=t;if(typeof e.generate_thumbnails!="boolean")return i.errors=[{instancePath:s+"/generate_thumbnails",schemaPath:"#/properties/generate_thumbnails/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var r=o===t}else var r=!0;if(r)if(e.thumbnails_per_recording!==void 0){let o=t;l(e.thumbnails_per_recording,{instancePath:s+"/thumbnails_per_recording",parentData:e,parentDataProperty:"thumbnails_per_recording",rootData:c,dynamicAnchors:d})||(a=a===null?l.errors:a.concat(l.errors),t=a.length);var r=o===t}else var r=!0}}}}}}}}}}}}}}}}else return i.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return i.errors=a,t===0}i.evaluated={props:{go2rtc_enabled:!0,web_auth_enabled:!0,demo_mode:!0,audio_disabled:!0,auto_disabled:!0,go2rtc_available:!0,go2rtc_api_port:!0,go2rtc_base_path:!0,webrtc_disabled:!0,hls_disabled:!0,mse_disabled:!0,default_playback_transport:!0,go2rtc_force_native_hls:!0,webrtc_connection_timeout_ms:!0,webrtc_ice_recovery_timeout_ms:!0,generate_thumbnails:!0,thumbnails_per_recording:!0},dynamicProps:!1,dynamicItems:!1};export{f as validateClientConfig}; diff --git a/web/js/api/generated/validators/DetailStatus.d.mts b/web/js/api/generated/validators/DetailStatus.d.mts new file mode 100644 index 000000000..ed82bcba2 --- /dev/null +++ b/web/js/api/generated/validators/DetailStatus.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateDetailStatus } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/DetailStatus.mjs b/web/js/api/generated/validators/DetailStatus.mjs new file mode 100644 index 000000000..4346faf49 --- /dev/null +++ b/web/js/api/generated/validators/DetailStatus.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var m=n,a={type:"string",enum:["Running","Degraded","Error","Stopped","Unknown","Starting","Stopping","Reconnecting"],$id:"urn:lightnvr:contract:DetailStatus"};function n(e,{instancePath:t="",parentData:i,parentDataProperty:u,rootData:l=e,dynamicAnchors:p={}}={}){let s=null,o=0,r=n.evaluated;return r.dynamicProps&&(r.props=void 0),r.dynamicItems&&(r.items=void 0),typeof e!="string"?(n.errors=[{instancePath:t,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1):e==="Running"||e==="Degraded"||e==="Error"||e==="Stopped"||e==="Unknown"||e==="Starting"||e==="Stopping"||e==="Reconnecting"?(n.errors=s,o===0):(n.errors=[{instancePath:t,schemaPath:"#/enum",keyword:"enum",params:{allowedValues:a.enum},message:"must be equal to one of the allowed values"}],!1)}n.evaluated={dynamicProps:!1,dynamicItems:!1};export{m as validateDetailStatus}; diff --git a/web/js/api/generated/validators/DetectionModel.d.mts b/web/js/api/generated/validators/DetectionModel.d.mts new file mode 100644 index 000000000..7815481f4 --- /dev/null +++ b/web/js/api/generated/validators/DetectionModel.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateDetectionModel } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/DetectionModel.mjs b/web/js/api/generated/validators/DetectionModel.mjs new file mode 100644 index 000000000..617058187 --- /dev/null +++ b/web/js/api/generated/validators/DetectionModel.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var v=i;function i(e,{instancePath:o="",parentData:l,parentDataProperty:c,rootData:h=e,dynamicAnchors:g={}}={}){let p=null,r=0,a=i.evaluated;a.dynamicProps&&(a.props=void 0),a.dynamicItems&&(a.items=void 0);let f=r,d=!0,y=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let n;if(e.supported===void 0&&(n="supported")){let s={};p===null?p=[s]:p.push(s),r++}else if(e.supported!==void 0&&e.supported!==!1){let s={};p===null?p=[s]:p.push(s),r++}}var m=y===r;if(r=f,p!==null&&(f?p.length=f:p=null),m){let n=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let u;if(e.unsupported_reason===void 0&&(u="unsupported_reason"))return i.errors=[{instancePath:o,schemaPath:"#/then/required",keyword:"required",params:{missingProperty:u},message:"must have required property '"+u+"'"}],!1}var m=n===r;d=m}if(!d){let n={instancePath:o,schemaPath:"#/if",keyword:"if",params:{failingKeyword:"then"},message:'must match "then" schema'};return p===null?p=[n]:p.push(n),r++,i.errors=p,!1}if(r===0)if(e&&typeof e=="object"&&!Array.isArray(e)){let n;if(e.id===void 0&&(n="id")||e.name===void 0&&(n="name")||e.path===void 0&&(n="path")||e.type===void 0&&(n="type")||e.supported===void 0&&(n="supported"))return i.errors=[{instancePath:o,schemaPath:"#/required",keyword:"required",params:{missingProperty:n},message:"must have required property '"+n+"'"}],!1;if(e.id!==void 0){let s=r;if(typeof e.id!="string")return i.errors=[{instancePath:o+"/id",schemaPath:"#/properties/id/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var t=s===r}else var t=!0;if(t){if(e.name!==void 0){let s=r;if(typeof e.name!="string")return i.errors=[{instancePath:o+"/name",schemaPath:"#/properties/name/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var t=s===r}else var t=!0;if(t){if(e.path!==void 0){let s=r;if(typeof e.path!="string")return i.errors=[{instancePath:o+"/path",schemaPath:"#/properties/path/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var t=s===r}else var t=!0;if(t){if(e.type!==void 0){let s=r;if(typeof e.type!="string")return i.errors=[{instancePath:o+"/type",schemaPath:"#/properties/type/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var t=s===r}else var t=!0;if(t){if(e.supported!==void 0){let s=r;if(typeof e.supported!="boolean")return i.errors=[{instancePath:o+"/supported",schemaPath:"#/properties/supported/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var t=s===r}else var t=!0;if(t){if(e.description!==void 0){let s=r;if(typeof e.description!="string")return i.errors=[{instancePath:o+"/description",schemaPath:"#/properties/description/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var t=s===r}else var t=!0;if(t){if(e.unsupported_reason!==void 0){let s=r;if(typeof e.unsupported_reason!="string")return i.errors=[{instancePath:o+"/unsupported_reason",schemaPath:"#/properties/unsupported_reason/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var t=s===r}else var t=!0;if(t)if(e.size!==void 0){let s=e.size,u=r;if(r===u)if(typeof s=="number"&&isFinite(s)){if(s<0||isNaN(s))return i.errors=[{instancePath:o+"/size",schemaPath:"#/properties/size/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1}else return i.errors=[{instancePath:o+"/size",schemaPath:"#/properties/size/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var t=u===r}else var t=!0}}}}}}}else return i.errors=[{instancePath:o,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return i.errors=p,r===0}i.evaluated={props:{id:!0,name:!0,path:!0,type:!0,supported:!0,description:!0,unsupported_reason:!0,size:!0},dynamicProps:!1,dynamicItems:!1};export{v as validateDetectionModel}; diff --git a/web/js/api/generated/validators/DetectionModelsResponse.d.mts b/web/js/api/generated/validators/DetectionModelsResponse.d.mts new file mode 100644 index 000000000..965d75cb6 --- /dev/null +++ b/web/js/api/generated/validators/DetectionModelsResponse.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateDetectionModelsResponse } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/DetectionModelsResponse.mjs b/web/js/api/generated/validators/DetectionModelsResponse.mjs new file mode 100644 index 000000000..34b2b9a06 --- /dev/null +++ b/web/js/api/generated/validators/DetectionModelsResponse.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var b=u;function a(e,{instancePath:i="",parentData:h,parentDataProperty:g,rootData:c=e,dynamicAnchors:v={}}={}){let n=null,r=0,m=a.evaluated;m.dynamicProps&&(m.props=void 0),m.dynamicItems&&(m.items=void 0);let y=r,p=!0,d=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let l;if(e.supported===void 0&&(l="supported")){let s={};n===null?n=[s]:n.push(s),r++}else if(e.supported!==void 0&&e.supported!==!1){let s={};n===null?n=[s]:n.push(s),r++}}var o=d===r;if(r=y,n!==null&&(y?n.length=y:n=null),o){let l=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let f;if(e.unsupported_reason===void 0&&(f="unsupported_reason"))return a.errors=[{instancePath:i,schemaPath:"#/then/required",keyword:"required",params:{missingProperty:f},message:"must have required property '"+f+"'"}],!1}var o=l===r;p=o}if(!p){let l={instancePath:i,schemaPath:"#/if",keyword:"if",params:{failingKeyword:"then"},message:'must match "then" schema'};return n===null?n=[l]:n.push(l),r++,a.errors=n,!1}if(r===0)if(e&&typeof e=="object"&&!Array.isArray(e)){let l;if(e.id===void 0&&(l="id")||e.name===void 0&&(l="name")||e.path===void 0&&(l="path")||e.type===void 0&&(l="type")||e.supported===void 0&&(l="supported"))return a.errors=[{instancePath:i,schemaPath:"#/required",keyword:"required",params:{missingProperty:l},message:"must have required property '"+l+"'"}],!1;if(e.id!==void 0){let s=r;if(typeof e.id!="string")return a.errors=[{instancePath:i+"/id",schemaPath:"#/properties/id/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var t=s===r}else var t=!0;if(t){if(e.name!==void 0){let s=r;if(typeof e.name!="string")return a.errors=[{instancePath:i+"/name",schemaPath:"#/properties/name/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var t=s===r}else var t=!0;if(t){if(e.path!==void 0){let s=r;if(typeof e.path!="string")return a.errors=[{instancePath:i+"/path",schemaPath:"#/properties/path/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var t=s===r}else var t=!0;if(t){if(e.type!==void 0){let s=r;if(typeof e.type!="string")return a.errors=[{instancePath:i+"/type",schemaPath:"#/properties/type/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var t=s===r}else var t=!0;if(t){if(e.supported!==void 0){let s=r;if(typeof e.supported!="boolean")return a.errors=[{instancePath:i+"/supported",schemaPath:"#/properties/supported/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var t=s===r}else var t=!0;if(t){if(e.description!==void 0){let s=r;if(typeof e.description!="string")return a.errors=[{instancePath:i+"/description",schemaPath:"#/properties/description/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var t=s===r}else var t=!0;if(t){if(e.unsupported_reason!==void 0){let s=r;if(typeof e.unsupported_reason!="string")return a.errors=[{instancePath:i+"/unsupported_reason",schemaPath:"#/properties/unsupported_reason/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var t=s===r}else var t=!0;if(t)if(e.size!==void 0){let s=e.size,f=r;if(r===f)if(typeof s=="number"&&isFinite(s)){if(s<0||isNaN(s))return a.errors=[{instancePath:i+"/size",schemaPath:"#/properties/size/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1}else return a.errors=[{instancePath:i+"/size",schemaPath:"#/properties/size/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var t=f===r}else var t=!0}}}}}}}else return a.errors=[{instancePath:i,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return a.errors=n,r===0}a.evaluated={props:{id:!0,name:!0,path:!0,type:!0,supported:!0,description:!0,unsupported_reason:!0,size:!0},dynamicProps:!1,dynamicItems:!1};function u(e,{instancePath:i="",parentData:h,parentDataProperty:g,rootData:c=e,dynamicAnchors:v={}}={}){let n=null,r=0,m=u.evaluated;if(m.dynamicProps&&(m.props=void 0),m.dynamicItems&&(m.items=void 0),r===0)if(e&&typeof e=="object"&&!Array.isArray(e)){let d;if(e.models===void 0&&(d="models")||e.litert_available===void 0&&(d="litert_available")||e.sod_available===void 0&&(d="sod_available")||e.count===void 0&&(d="count"))return u.errors=[{instancePath:i,schemaPath:"#/required",keyword:"required",params:{missingProperty:d},message:"must have required property '"+d+"'"}],!1;if(e.models!==void 0){let o=e.models,t=r;if(r===t)if(Array.isArray(o)){if(o.length<3)return u.errors=[{instancePath:i+"/models",schemaPath:"#/properties/models/minItems",keyword:"minItems",params:{limit:3},message:"must NOT have fewer than 3 items"}],!1;{var y=!0;let l=o.length;for(let s=0;s=",limit:3},message:"must be >= 3"}],!1;var p=t===r}else var p=!0;if(p)if(e.message!==void 0){let o=r;if(e.message!=="Models directory does not exist")return u.errors=[{instancePath:i+"/message",schemaPath:"#/properties/message/const",keyword:"const",params:{allowedValue:"Models directory does not exist"},message:"must be equal to constant"}],!1;var p=o===r}else var p=!0}}}}else return u.errors=[{instancePath:i,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return u.errors=n,r===0}u.evaluated={props:{models:!0,litert_available:!0,sod_available:!0,count:!0,message:!0},dynamicProps:!1,dynamicItems:!1};export{b as validateDetectionModelsResponse}; diff --git a/web/js/api/generated/validators/DetectionUrl.d.mts b/web/js/api/generated/validators/DetectionUrl.d.mts new file mode 100644 index 000000000..bb73289bf --- /dev/null +++ b/web/js/api/generated/validators/DetectionUrl.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateDetectionUrl } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/DetectionUrl.mjs b/web/js/api/generated/validators/DetectionUrl.mjs new file mode 100644 index 000000000..733ebab1d --- /dev/null +++ b/web/js/api/generated/validators/DetectionUrl.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var p=(e,t)=>()=>{try{return t||e((t={exports:{}}).exports,t),t.exports}catch(a){throw t=0,a}};var c=p(i=>{"use strict";Object.defineProperty(i,"__esModule",{value:!0});function u(e){let t=e.length,a=0,s=0,n;for(;s=55296&&n<=56319&&s511)return r.errors=[{instancePath:t,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1;if(d.test(e)){if(new TextEncoder().encode(e).byteLength>511)return r.errors=[{instancePath:t,schemaPath:"#/x-lightnvr-max-bytes",keyword:"x-lightnvr-max-bytes",params:{},message:"must fit the UTF-8 byte limit"}],!1}else return r.errors=[{instancePath:t,schemaPath:"#/pattern",keyword:"pattern",params:{pattern:"^($|([Hh][Tt][Tt][Pp][Ss]?|[Rr][Tt][Ss][Pp][Ss]?)://)"},message:'must match pattern "^($|([Hh][Tt][Tt][Pp][Ss]?|[Rr][Tt][Ss][Pp][Ss]?)://)"'}],!1}return r.errors=m,o===0}r.evaluated={dynamicProps:!1,dynamicItems:!1};export{T as validateDetectionUrl}; diff --git a/web/js/api/generated/validators/DetectionZone.d.mts b/web/js/api/generated/validators/DetectionZone.d.mts new file mode 100644 index 000000000..efa7b8bf6 --- /dev/null +++ b/web/js/api/generated/validators/DetectionZone.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateDetectionZone } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/DetectionZone.mjs b/web/js/api/generated/validators/DetectionZone.mjs new file mode 100644 index 000000000..496c4db5e --- /dev/null +++ b/web/js/api/generated/validators/DetectionZone.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var k=(e,t)=>()=>{try{return t||e((t={exports:{}}).exports,t),t.exports}catch(y){throw t=0,y}};var x=k(b=>{"use strict";Object.defineProperty(b,"__esModule",{value:!0});function _(e){let t=e.length,y=0,c=0,p;for(;c=55296&&p<=56319&&c255)return f.errors=[{instancePath:t,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1;if(h(e)<1)return f.errors=[{instancePath:t,schemaPath:"#/minLength",keyword:"minLength",params:{limit:1},message:"must NOT have fewer than 1 characters"}],!1;if(new TextEncoder().encode(e).byteLength>255)return f.errors=[{instancePath:t,schemaPath:"#/x-lightnvr-max-bytes",keyword:"x-lightnvr-max-bytes",params:{},message:"must fit the UTF-8 byte limit"}],!1}else return f.errors=[{instancePath:t,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return f.errors=l,r===0}f.evaluated={dynamicProps:!1,dynamicItems:!1};function u(e,{instancePath:t="",parentData:y,parentDataProperty:c,rootData:p=e,dynamicAnchors:v={}}={}){let l=null,r=0,m=u.evaluated;if(m.dynamicProps&&(m.props=void 0),m.dynamicItems&&(m.items=void 0),r===0)if(e&&typeof e=="object"&&!Array.isArray(e)){let d;if(e.x===void 0&&(d="x")||e.y===void 0&&(d="y"))return u.errors=[{instancePath:t,schemaPath:"#/required",keyword:"required",params:{missingProperty:d},message:"must have required property '"+d+"'"}],!1;if(e.x!==void 0){let i=e.x,s=r;if(!(typeof i=="number"&&isFinite(i)))return u.errors=[{instancePath:t+"/x",schemaPath:"#/properties/x/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var n=s===r}else var n=!0;if(n)if(e.y!==void 0){let i=e.y,s=r;if(!(typeof i=="number"&&isFinite(i)))return u.errors=[{instancePath:t+"/y",schemaPath:"#/properties/y/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var n=s===r}else var n=!0}else return u.errors=[{instancePath:t,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return u.errors=l,r===0}u.evaluated={props:{x:!0,y:!0},dynamicProps:!1,dynamicItems:!1};function a(e,{instancePath:t="",parentData:y,parentDataProperty:c,rootData:p=e,dynamicAnchors:v={}}={}){let l=null,r=0,m=a.evaluated;if(m.dynamicProps&&(m.props=void 0),m.dynamicItems&&(m.items=void 0),r===0)if(e&&typeof e=="object"&&!Array.isArray(e)){let i;if(e.id===void 0&&(i="id")||e.stream_name===void 0&&(i="stream_name")||e.name===void 0&&(i="name")||e.enabled===void 0&&(i="enabled")||e.color===void 0&&(i="color")||e.polygon===void 0&&(i="polygon")||e.filter_classes===void 0&&(i="filter_classes")||e.min_confidence===void 0&&(i="min_confidence"))return a.errors=[{instancePath:t,schemaPath:"#/required",keyword:"required",params:{missingProperty:i},message:"must have required property '"+i+"'"}],!1;if(e.id!==void 0){let s=e.id,o=r;if(r===o)if(typeof s=="string"){if(h(s)>63)return a.errors=[{instancePath:t+"/id",schemaPath:"#/properties/id/maxLength",keyword:"maxLength",params:{limit:63},message:"must NOT have more than 63 characters"}],!1}else return a.errors=[{instancePath:t+"/id",schemaPath:"#/properties/id/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=o===r}else var n=!0;if(n){if(e.stream_name!==void 0){let s=r;f(e.stream_name,{instancePath:t+"/stream_name",parentData:e,parentDataProperty:"stream_name",rootData:p,dynamicAnchors:v})||(l=l===null?f.errors:l.concat(f.errors),r=l.length);var n=s===r}else var n=!0;if(n){if(e.name!==void 0){let s=e.name,o=r;if(r===o)if(typeof s=="string"){if(h(s)>63)return a.errors=[{instancePath:t+"/name",schemaPath:"#/properties/name/maxLength",keyword:"maxLength",params:{limit:63},message:"must NOT have more than 63 characters"}],!1}else return a.errors=[{instancePath:t+"/name",schemaPath:"#/properties/name/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=o===r}else var n=!0;if(n){if(e.enabled!==void 0){let s=r;if(typeof e.enabled!="boolean")return a.errors=[{instancePath:t+"/enabled",schemaPath:"#/properties/enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=s===r}else var n=!0;if(n){if(e.color!==void 0){let s=e.color,o=r;if(r===o)if(typeof s=="string"){if(h(s)>7)return a.errors=[{instancePath:t+"/color",schemaPath:"#/properties/color/maxLength",keyword:"maxLength",params:{limit:7},message:"must NOT have more than 7 characters"}],!1}else return a.errors=[{instancePath:t+"/color",schemaPath:"#/properties/color/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=o===r}else var n=!0;if(n){if(e.polygon!==void 0){let s=e.polygon,o=r;if(r===o)if(Array.isArray(s)){if(s.length>32)return a.errors=[{instancePath:t+"/polygon",schemaPath:"#/properties/polygon/maxItems",keyword:"maxItems",params:{limit:32},message:"must NOT have more than 32 items"}],!1;{var d=!0;let P=s.length;for(let g=0;g255)return a.errors=[{instancePath:t+"/filter_classes",schemaPath:"#/properties/filter_classes/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1}else return a.errors=[{instancePath:t+"/filter_classes",schemaPath:"#/properties/filter_classes/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=o===r}else var n=!0;if(n)if(e.min_confidence!==void 0){let s=e.min_confidence,o=r;if(!(typeof s=="number"&&isFinite(s)))return a.errors=[{instancePath:t+"/min_confidence",schemaPath:"#/properties/min_confidence/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var n=o===r}else var n=!0}}}}}}}else return a.errors=[{instancePath:t,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return a.errors=l,r===0}a.evaluated={props:{id:!0,stream_name:!0,name:!0,enabled:!0,color:!0,polygon:!0,filter_classes:!0,min_confidence:!0},dynamicProps:!1,dynamicItems:!1};export{j as validateDetectionZone}; diff --git a/web/js/api/generated/validators/EmptyString.d.mts b/web/js/api/generated/validators/EmptyString.d.mts new file mode 100644 index 000000000..6933cf56a --- /dev/null +++ b/web/js/api/generated/validators/EmptyString.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateEmptyString } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/EmptyString.mjs b/web/js/api/generated/validators/EmptyString.mjs new file mode 100644 index 000000000..daf6e0598 --- /dev/null +++ b/web/js/api/generated/validators/EmptyString.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var l=e;function e(r,{instancePath:s="",parentData:o,parentDataProperty:i,rootData:c=r,dynamicAnchors:m={}}={}){let a=null,n=0,t=e.evaluated;return t.dynamicProps&&(t.props=void 0),t.dynamicItems&&(t.items=void 0),typeof r!="string"?(e.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1):r!==""?(e.errors=[{instancePath:s,schemaPath:"#/const",keyword:"const",params:{allowedValue:""},message:"must be equal to constant"}],!1):(e.errors=a,n===0)}e.evaluated={dynamicProps:!1,dynamicItems:!1};export{l as validateEmptyString}; diff --git a/web/js/api/generated/validators/EpochSecondsOrZero.d.mts b/web/js/api/generated/validators/EpochSecondsOrZero.d.mts new file mode 100644 index 000000000..dda67df3f --- /dev/null +++ b/web/js/api/generated/validators/EpochSecondsOrZero.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateEpochSecondsOrZero } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/EpochSecondsOrZero.mjs b/web/js/api/generated/validators/EpochSecondsOrZero.mjs new file mode 100644 index 000000000..563969800 --- /dev/null +++ b/web/js/api/generated/validators/EpochSecondsOrZero.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var p=r;function r(e,{instancePath:s="",parentData:m,parentDataProperty:o,rootData:a=e,dynamicAnchors:c={}}={}){let n=null,t=0,i=r.evaluated;return i.dynamicProps&&(i.props=void 0),i.dynamicItems&&(i.items=void 0),typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)?t===0&&typeof e=="number"&&isFinite(e)&&(e<0||isNaN(e))?(r.errors=[{instancePath:s,schemaPath:"#/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1):(r.errors=n,t===0):(r.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1)}r.evaluated={dynamicProps:!1,dynamicItems:!1};export{p as validateEpochSecondsOrZero}; diff --git a/web/js/api/generated/validators/EptzConfigString.d.mts b/web/js/api/generated/validators/EptzConfigString.d.mts new file mode 100644 index 000000000..cce369569 --- /dev/null +++ b/web/js/api/generated/validators/EptzConfigString.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateEptzConfigString } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/EptzConfigString.mjs b/web/js/api/generated/validators/EptzConfigString.mjs new file mode 100644 index 000000000..1e28ef18e --- /dev/null +++ b/web/js/api/generated/validators/EptzConfigString.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var b=(r,t)=>()=>{try{return t||r((t={exports:{}}).exports,t),t.exports}catch(c){throw t=0,c}};var v=b(d=>{"use strict";Object.defineProperty(d,"__esModule",{value:!0});function h(r){let t=r.length,c=0,u=0,l;for(;u=55296&&l<=56319&&u1||isNaN(e))return i.errors=[{instancePath:t+"/centerX",schemaPath:"#/properties/centerX/maximum",keyword:"maximum",params:{comparison:"<=",limit:1},message:"must be <= 1"}],!1;if(e<0||isNaN(e))return i.errors=[{instancePath:t+"/centerX",schemaPath:"#/properties/centerX/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1}else return i.errors=[{instancePath:t+"/centerX",schemaPath:"#/properties/centerX/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var a=m===s}else var a=!0;if(a){if(r.centerY!==void 0){let e=r.centerY,m=s;if(s===m)if(typeof e=="number"&&isFinite(e)){if(e>1||isNaN(e))return i.errors=[{instancePath:t+"/centerY",schemaPath:"#/properties/centerY/maximum",keyword:"maximum",params:{comparison:"<=",limit:1},message:"must be <= 1"}],!1;if(e<0||isNaN(e))return i.errors=[{instancePath:t+"/centerY",schemaPath:"#/properties/centerY/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1}else return i.errors=[{instancePath:t+"/centerY",schemaPath:"#/properties/centerY/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var a=m===s}else var a=!0;if(a){if(r.radius!==void 0){let e=r.radius,m=s;if(s===m)if(typeof e=="number"&&isFinite(e)){if(e>1||isNaN(e))return i.errors=[{instancePath:t+"/radius",schemaPath:"#/properties/radius/maximum",keyword:"maximum",params:{comparison:"<=",limit:1},message:"must be <= 1"}],!1;if(e<.05||isNaN(e))return i.errors=[{instancePath:t+"/radius",schemaPath:"#/properties/radius/minimum",keyword:"minimum",params:{comparison:">=",limit:.05},message:"must be >= 0.05"}],!1}else return i.errors=[{instancePath:t+"/radius",schemaPath:"#/properties/radius/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var a=m===s}else var a=!0;if(a){if(r.fov!==void 0){let e=r.fov,m=s;if(s===m)if(typeof e=="number"&&isFinite(e)){if(e>360||isNaN(e))return i.errors=[{instancePath:t+"/fov",schemaPath:"#/properties/fov/maximum",keyword:"maximum",params:{comparison:"<=",limit:360},message:"must be <= 360"}],!1;if(e<160||isNaN(e))return i.errors=[{instancePath:t+"/fov",schemaPath:"#/properties/fov/minimum",keyword:"minimum",params:{comparison:">=",limit:160},message:"must be >= 160"}],!1}else return i.errors=[{instancePath:t+"/fov",schemaPath:"#/properties/fov/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var a=m===s}else var a=!0;if(a){if(r.rotation!==void 0){let e=r.rotation,m=s;if(s===m)if(typeof e=="number"&&isFinite(e)){if(e>360||isNaN(e))return i.errors=[{instancePath:t+"/rotation",schemaPath:"#/properties/rotation/maximum",keyword:"maximum",params:{comparison:"<=",limit:360},message:"must be <= 360"}],!1;if(e<-360||isNaN(e))return i.errors=[{instancePath:t+"/rotation",schemaPath:"#/properties/rotation/minimum",keyword:"minimum",params:{comparison:">=",limit:-360},message:"must be >= -360"}],!1}else return i.errors=[{instancePath:t+"/rotation",schemaPath:"#/properties/rotation/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var a=m===s}else var a=!0;if(a){if(r.defaultYaw!==void 0){let e=r.defaultYaw,m=s;if(s===m)if(typeof e=="number"&&isFinite(e)){if(e>360||isNaN(e))return i.errors=[{instancePath:t+"/defaultYaw",schemaPath:"#/properties/defaultYaw/maximum",keyword:"maximum",params:{comparison:"<=",limit:360},message:"must be <= 360"}],!1;if(e<-360||isNaN(e))return i.errors=[{instancePath:t+"/defaultYaw",schemaPath:"#/properties/defaultYaw/minimum",keyword:"minimum",params:{comparison:">=",limit:-360},message:"must be >= -360"}],!1}else return i.errors=[{instancePath:t+"/defaultYaw",schemaPath:"#/properties/defaultYaw/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var a=m===s}else var a=!0;if(a){if(r.defaultTilt!==void 0){let e=r.defaultTilt,m=s;if(s===m)if(typeof e=="number"&&isFinite(e)){if(e>30||isNaN(e))return i.errors=[{instancePath:t+"/defaultTilt",schemaPath:"#/properties/defaultTilt/maximum",keyword:"maximum",params:{comparison:"<=",limit:30},message:"must be <= 30"}],!1;if(e<-90||isNaN(e))return i.errors=[{instancePath:t+"/defaultTilt",schemaPath:"#/properties/defaultTilt/minimum",keyword:"minimum",params:{comparison:">=",limit:-90},message:"must be >= -90"}],!1}else return i.errors=[{instancePath:t+"/defaultTilt",schemaPath:"#/properties/defaultTilt/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var a=m===s}else var a=!0;if(a)if(r.defaultViewFov!==void 0){let e=r.defaultViewFov,m=s;if(s===m)if(typeof e=="number"&&isFinite(e)){if(e>120||isNaN(e))return i.errors=[{instancePath:t+"/defaultViewFov",schemaPath:"#/properties/defaultViewFov/maximum",keyword:"maximum",params:{comparison:"<=",limit:120},message:"must be <= 120"}],!1;if(e<20||isNaN(e))return i.errors=[{instancePath:t+"/defaultViewFov",schemaPath:"#/properties/defaultViewFov/minimum",keyword:"minimum",params:{comparison:">=",limit:20},message:"must be >= 20"}],!1}else return i.errors=[{instancePath:t+"/defaultViewFov",schemaPath:"#/properties/defaultViewFov/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var a=m===s}else var a=!0}}}}}}}}}}else return i.errors=[{instancePath:t,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return i.errors=p,s===0}i.evaluated={props:{version:!0,projection:!0,mount:!0,centerX:!0,centerY:!0,radius:!0,fov:!0,rotation:!0,defaultYaw:!0,defaultTilt:!0,defaultViewFov:!0},dynamicProps:!1,dynamicItems:!1};function f(r,{instancePath:t="",parentData:c,parentDataProperty:u,rootData:l=r,dynamicAnchors:y={}}={}){let p=null,s=0,o=f.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),s===0)if(typeof r=="string"){if(g(r)>1023)return f.errors=[{instancePath:t,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:1023},message:"must NOT have more than 1023 characters"}],!1;if(new TextEncoder().encode(r).byteLength>1023)return f.errors=[{instancePath:t,schemaPath:"#/x-lightnvr-max-bytes",keyword:"x-lightnvr-max-bytes",params:{},message:"must fit the UTF-8 byte limit"}],!1;if(r!==""){let n,e=!0;try{n=JSON.parse(r)}catch{e=!1}if(e){let m=s;i(n,{instancePath:t,parentData:r,parentDataProperty:u,rootData:l,dynamicAnchors:y})||(p=p===null?i.errors:p.concat(i.errors),s=p.length);var a=m===s;e=a}if(!e)return f.errors=[{instancePath:t,schemaPath:"#/contentSchema",keyword:"contentSchema",params:{},message:"must contain valid JSON matching contentSchema (or be empty)"}],!1}}else return f.errors=[{instancePath:t,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return f.errors=p,s===0}f.evaluated={dynamicProps:!1,dynamicItems:!1};export{N as validateEptzConfigString}; diff --git a/web/js/api/generated/validators/EptzConfigV1.d.mts b/web/js/api/generated/validators/EptzConfigV1.d.mts new file mode 100644 index 000000000..d2efa12b0 --- /dev/null +++ b/web/js/api/generated/validators/EptzConfigV1.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateEptzConfigV1 } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/EptzConfigV1.mjs b/web/js/api/generated/validators/EptzConfigV1.mjs new file mode 100644 index 000000000..b5ba71c36 --- /dev/null +++ b/web/js/api/generated/validators/EptzConfigV1.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var c=i;function i(r,{instancePath:m="",parentData:f,parentDataProperty:l,rootData:p=r,dynamicAnchors:d={}}={}){let u=null,s=0,o=i.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),s===0)if(r&&typeof r=="object"&&!Array.isArray(r)){let n;if(r.version===void 0&&(n="version")||r.projection===void 0&&(n="projection")||r.mount===void 0&&(n="mount")||r.centerX===void 0&&(n="centerX")||r.centerY===void 0&&(n="centerY")||r.radius===void 0&&(n="radius")||r.fov===void 0&&(n="fov")||r.rotation===void 0&&(n="rotation")||r.defaultYaw===void 0&&(n="defaultYaw")||r.defaultTilt===void 0&&(n="defaultTilt")||r.defaultViewFov===void 0&&(n="defaultViewFov"))return i.errors=[{instancePath:m,schemaPath:"#/required",keyword:"required",params:{missingProperty:n},message:"must have required property '"+n+"'"}],!1;if(r.version!==void 0){let e=s;if(r.version!==1)return i.errors=[{instancePath:m+"/version",schemaPath:"#/properties/version/const",keyword:"const",params:{allowedValue:1},message:"must be equal to constant"}],!1;var t=e===s}else var t=!0;if(t){if(r.projection!==void 0){let e=s;if(r.projection!=="equidistant")return i.errors=[{instancePath:m+"/projection",schemaPath:"#/properties/projection/const",keyword:"const",params:{allowedValue:"equidistant"},message:"must be equal to constant"}],!1;var t=e===s}else var t=!0;if(t){if(r.mount!==void 0){let e=s;if(r.mount!=="ceiling")return i.errors=[{instancePath:m+"/mount",schemaPath:"#/properties/mount/const",keyword:"const",params:{allowedValue:"ceiling"},message:"must be equal to constant"}],!1;var t=e===s}else var t=!0;if(t){if(r.centerX!==void 0){let e=r.centerX,a=s;if(s===a)if(typeof e=="number"&&isFinite(e)){if(e>1||isNaN(e))return i.errors=[{instancePath:m+"/centerX",schemaPath:"#/properties/centerX/maximum",keyword:"maximum",params:{comparison:"<=",limit:1},message:"must be <= 1"}],!1;if(e<0||isNaN(e))return i.errors=[{instancePath:m+"/centerX",schemaPath:"#/properties/centerX/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1}else return i.errors=[{instancePath:m+"/centerX",schemaPath:"#/properties/centerX/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var t=a===s}else var t=!0;if(t){if(r.centerY!==void 0){let e=r.centerY,a=s;if(s===a)if(typeof e=="number"&&isFinite(e)){if(e>1||isNaN(e))return i.errors=[{instancePath:m+"/centerY",schemaPath:"#/properties/centerY/maximum",keyword:"maximum",params:{comparison:"<=",limit:1},message:"must be <= 1"}],!1;if(e<0||isNaN(e))return i.errors=[{instancePath:m+"/centerY",schemaPath:"#/properties/centerY/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1}else return i.errors=[{instancePath:m+"/centerY",schemaPath:"#/properties/centerY/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var t=a===s}else var t=!0;if(t){if(r.radius!==void 0){let e=r.radius,a=s;if(s===a)if(typeof e=="number"&&isFinite(e)){if(e>1||isNaN(e))return i.errors=[{instancePath:m+"/radius",schemaPath:"#/properties/radius/maximum",keyword:"maximum",params:{comparison:"<=",limit:1},message:"must be <= 1"}],!1;if(e<.05||isNaN(e))return i.errors=[{instancePath:m+"/radius",schemaPath:"#/properties/radius/minimum",keyword:"minimum",params:{comparison:">=",limit:.05},message:"must be >= 0.05"}],!1}else return i.errors=[{instancePath:m+"/radius",schemaPath:"#/properties/radius/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var t=a===s}else var t=!0;if(t){if(r.fov!==void 0){let e=r.fov,a=s;if(s===a)if(typeof e=="number"&&isFinite(e)){if(e>360||isNaN(e))return i.errors=[{instancePath:m+"/fov",schemaPath:"#/properties/fov/maximum",keyword:"maximum",params:{comparison:"<=",limit:360},message:"must be <= 360"}],!1;if(e<160||isNaN(e))return i.errors=[{instancePath:m+"/fov",schemaPath:"#/properties/fov/minimum",keyword:"minimum",params:{comparison:">=",limit:160},message:"must be >= 160"}],!1}else return i.errors=[{instancePath:m+"/fov",schemaPath:"#/properties/fov/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var t=a===s}else var t=!0;if(t){if(r.rotation!==void 0){let e=r.rotation,a=s;if(s===a)if(typeof e=="number"&&isFinite(e)){if(e>360||isNaN(e))return i.errors=[{instancePath:m+"/rotation",schemaPath:"#/properties/rotation/maximum",keyword:"maximum",params:{comparison:"<=",limit:360},message:"must be <= 360"}],!1;if(e<-360||isNaN(e))return i.errors=[{instancePath:m+"/rotation",schemaPath:"#/properties/rotation/minimum",keyword:"minimum",params:{comparison:">=",limit:-360},message:"must be >= -360"}],!1}else return i.errors=[{instancePath:m+"/rotation",schemaPath:"#/properties/rotation/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var t=a===s}else var t=!0;if(t){if(r.defaultYaw!==void 0){let e=r.defaultYaw,a=s;if(s===a)if(typeof e=="number"&&isFinite(e)){if(e>360||isNaN(e))return i.errors=[{instancePath:m+"/defaultYaw",schemaPath:"#/properties/defaultYaw/maximum",keyword:"maximum",params:{comparison:"<=",limit:360},message:"must be <= 360"}],!1;if(e<-360||isNaN(e))return i.errors=[{instancePath:m+"/defaultYaw",schemaPath:"#/properties/defaultYaw/minimum",keyword:"minimum",params:{comparison:">=",limit:-360},message:"must be >= -360"}],!1}else return i.errors=[{instancePath:m+"/defaultYaw",schemaPath:"#/properties/defaultYaw/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var t=a===s}else var t=!0;if(t){if(r.defaultTilt!==void 0){let e=r.defaultTilt,a=s;if(s===a)if(typeof e=="number"&&isFinite(e)){if(e>30||isNaN(e))return i.errors=[{instancePath:m+"/defaultTilt",schemaPath:"#/properties/defaultTilt/maximum",keyword:"maximum",params:{comparison:"<=",limit:30},message:"must be <= 30"}],!1;if(e<-90||isNaN(e))return i.errors=[{instancePath:m+"/defaultTilt",schemaPath:"#/properties/defaultTilt/minimum",keyword:"minimum",params:{comparison:">=",limit:-90},message:"must be >= -90"}],!1}else return i.errors=[{instancePath:m+"/defaultTilt",schemaPath:"#/properties/defaultTilt/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var t=a===s}else var t=!0;if(t)if(r.defaultViewFov!==void 0){let e=r.defaultViewFov,a=s;if(s===a)if(typeof e=="number"&&isFinite(e)){if(e>120||isNaN(e))return i.errors=[{instancePath:m+"/defaultViewFov",schemaPath:"#/properties/defaultViewFov/maximum",keyword:"maximum",params:{comparison:"<=",limit:120},message:"must be <= 120"}],!1;if(e<20||isNaN(e))return i.errors=[{instancePath:m+"/defaultViewFov",schemaPath:"#/properties/defaultViewFov/minimum",keyword:"minimum",params:{comparison:">=",limit:20},message:"must be >= 20"}],!1}else return i.errors=[{instancePath:m+"/defaultViewFov",schemaPath:"#/properties/defaultViewFov/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var t=a===s}else var t=!0}}}}}}}}}}else return i.errors=[{instancePath:m,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return i.errors=u,s===0}i.evaluated={props:{version:!0,projection:!0,mount:!0,centerX:!0,centerY:!0,radius:!0,fov:!0,rotation:!0,defaultYaw:!0,defaultTilt:!0,defaultViewFov:!0},dynamicProps:!1,dynamicItems:!1};export{c as validateEptzConfigV1}; diff --git a/web/js/api/generated/validators/ErrorResponse.d.mts b/web/js/api/generated/validators/ErrorResponse.d.mts new file mode 100644 index 000000000..5a936f24c --- /dev/null +++ b/web/js/api/generated/validators/ErrorResponse.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateErrorResponse } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/ErrorResponse.mjs b/web/js/api/generated/validators/ErrorResponse.mjs new file mode 100644 index 000000000..635a9700c --- /dev/null +++ b/web/js/api/generated/validators/ErrorResponse.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var m=(e,r)=>()=>{try{return r||e((r={exports:{}}).exports,r),r.exports}catch(n){throw r=0,n}};var d=m(c=>{"use strict";Object.defineProperty(c,"__esModule",{value:!0});function p(e){let r=e.length,n=0,s=0,o;for(;s=55296&&o<=56319&&s168)return e.errors=[{instancePath:t,schemaPath:"#/maxItems",keyword:"maxItems",params:{limit:168},message:"must NOT have more than 168 items"}],!1;if(r.length<168)return e.errors=[{instancePath:t,schemaPath:"#/minItems",keyword:"minItems",params:{limit:168},message:"must NOT have fewer than 168 items"}],!1;{var o=!0;let i=r.length;for(let m=0;m2147483647||isNaN(e))return r.errors=[{instancePath:m,schemaPath:"#/maximum",keyword:"maximum",params:{comparison:"<=",limit:2147483647},message:"must be <= 2147483647"}],!1;if(e<-2147483648||isNaN(e))return r.errors=[{instancePath:m,schemaPath:"#/minimum",keyword:"minimum",params:{comparison:">=",limit:-2147483648},message:"must be >= -2147483648"}],!1}return r.errors=t,s===0}r.evaluated={dynamicProps:!1,dynamicItems:!1};export{p as validateInt32}; diff --git a/web/js/api/generated/validators/LegacyStreamList.d.mts b/web/js/api/generated/validators/LegacyStreamList.d.mts new file mode 100644 index 000000000..644229458 --- /dev/null +++ b/web/js/api/generated/validators/LegacyStreamList.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateLegacyStreamList } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/LegacyStreamList.mjs b/web/js/api/generated/validators/LegacyStreamList.mjs new file mode 100644 index 000000000..dff3db7c7 --- /dev/null +++ b/web/js/api/generated/validators/LegacyStreamList.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var H=(e,n)=>()=>{try{return n||e((n={exports:{}}).exports,n),n.exports}catch(b){throw n=0,b}};var K=H(W=>{"use strict";Object.defineProperty(W,"__esModule",{value:!0});function Z(e){let n=e.length,b=0,g=0,c;for(;g=55296&&c<=56319&&g36)return N.errors=[{instancePath:n,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:36},message:"must NOT have more than 36 characters"}],!1;if(!J.test(e))return N.errors=[{instancePath:n,schemaPath:"#/pattern",keyword:"pattern",params:{pattern:"^(|[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})$"},message:'must match pattern "^(|[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})$"'}],!1}else return N.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return N.errors=t,r===0}N.evaluated={dynamicProps:!1,dynamicItems:!1};function I(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=I.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(typeof e=="string"){if(x(e)>255)return I.errors=[{instancePath:n,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1;if(x(e)<1)return I.errors=[{instancePath:n,schemaPath:"#/minLength",keyword:"minLength",params:{limit:1},message:"must NOT have fewer than 1 characters"}],!1;if(new TextEncoder().encode(e).byteLength>255)return I.errors=[{instancePath:n,schemaPath:"#/x-lightnvr-max-bytes",keyword:"x-lightnvr-max-bytes",params:{},message:"must fit the UTF-8 byte limit"}],!1}else return I.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return I.errors=t,r===0}I.evaluated={dynamicProps:!1,dynamicItems:!1};var M={type:"string",enum:["auto","webrtc_only","mse_only","hls_only","webrtc_then_mse","mse_then_hls"],$id:"urn:lightnvr:contract:PlaybackTransport"};function T(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=T.evaluated;return o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e!="string"?(T.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1):e==="auto"||e==="webrtc_only"||e==="mse_only"||e==="hls_only"||e==="webrtc_then_mse"||e==="mse_then_hls"?(T.errors=t,r===0):(T.errors=[{instancePath:n,schemaPath:"#/enum",keyword:"enum",params:{allowedValues:M.enum},message:"must be equal to one of the allowed values"}],!1)}T.evaluated={dynamicProps:!1,dynamicItems:!1};function v(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=v.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(e&&typeof e=="object"&&!Array.isArray(e)){let y;if(e.version===void 0&&(y="version")||e.projection===void 0&&(y="projection")||e.mount===void 0&&(y="mount")||e.centerX===void 0&&(y="centerX")||e.centerY===void 0&&(y="centerY")||e.radius===void 0&&(y="radius")||e.fov===void 0&&(y="fov")||e.rotation===void 0&&(y="rotation")||e.defaultYaw===void 0&&(y="defaultYaw")||e.defaultTilt===void 0&&(y="defaultTilt")||e.defaultViewFov===void 0&&(y="defaultViewFov"))return v.errors=[{instancePath:n,schemaPath:"#/required",keyword:"required",params:{missingProperty:y},message:"must have required property '"+y+"'"}],!1;if(e.version!==void 0){let a=r;if(e.version!==1)return v.errors=[{instancePath:n+"/version",schemaPath:"#/properties/version/const",keyword:"const",params:{allowedValue:1},message:"must be equal to constant"}],!1;var p=a===r}else var p=!0;if(p){if(e.projection!==void 0){let a=r;if(e.projection!=="equidistant")return v.errors=[{instancePath:n+"/projection",schemaPath:"#/properties/projection/const",keyword:"const",params:{allowedValue:"equidistant"},message:"must be equal to constant"}],!1;var p=a===r}else var p=!0;if(p){if(e.mount!==void 0){let a=r;if(e.mount!=="ceiling")return v.errors=[{instancePath:n+"/mount",schemaPath:"#/properties/mount/const",keyword:"const",params:{allowedValue:"ceiling"},message:"must be equal to constant"}],!1;var p=a===r}else var p=!0;if(p){if(e.centerX!==void 0){let a=e.centerX,l=r;if(r===l)if(typeof a=="number"&&isFinite(a)){if(a>1||isNaN(a))return v.errors=[{instancePath:n+"/centerX",schemaPath:"#/properties/centerX/maximum",keyword:"maximum",params:{comparison:"<=",limit:1},message:"must be <= 1"}],!1;if(a<0||isNaN(a))return v.errors=[{instancePath:n+"/centerX",schemaPath:"#/properties/centerX/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1}else return v.errors=[{instancePath:n+"/centerX",schemaPath:"#/properties/centerX/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var p=l===r}else var p=!0;if(p){if(e.centerY!==void 0){let a=e.centerY,l=r;if(r===l)if(typeof a=="number"&&isFinite(a)){if(a>1||isNaN(a))return v.errors=[{instancePath:n+"/centerY",schemaPath:"#/properties/centerY/maximum",keyword:"maximum",params:{comparison:"<=",limit:1},message:"must be <= 1"}],!1;if(a<0||isNaN(a))return v.errors=[{instancePath:n+"/centerY",schemaPath:"#/properties/centerY/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1}else return v.errors=[{instancePath:n+"/centerY",schemaPath:"#/properties/centerY/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var p=l===r}else var p=!0;if(p){if(e.radius!==void 0){let a=e.radius,l=r;if(r===l)if(typeof a=="number"&&isFinite(a)){if(a>1||isNaN(a))return v.errors=[{instancePath:n+"/radius",schemaPath:"#/properties/radius/maximum",keyword:"maximum",params:{comparison:"<=",limit:1},message:"must be <= 1"}],!1;if(a<.05||isNaN(a))return v.errors=[{instancePath:n+"/radius",schemaPath:"#/properties/radius/minimum",keyword:"minimum",params:{comparison:">=",limit:.05},message:"must be >= 0.05"}],!1}else return v.errors=[{instancePath:n+"/radius",schemaPath:"#/properties/radius/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var p=l===r}else var p=!0;if(p){if(e.fov!==void 0){let a=e.fov,l=r;if(r===l)if(typeof a=="number"&&isFinite(a)){if(a>360||isNaN(a))return v.errors=[{instancePath:n+"/fov",schemaPath:"#/properties/fov/maximum",keyword:"maximum",params:{comparison:"<=",limit:360},message:"must be <= 360"}],!1;if(a<160||isNaN(a))return v.errors=[{instancePath:n+"/fov",schemaPath:"#/properties/fov/minimum",keyword:"minimum",params:{comparison:">=",limit:160},message:"must be >= 160"}],!1}else return v.errors=[{instancePath:n+"/fov",schemaPath:"#/properties/fov/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var p=l===r}else var p=!0;if(p){if(e.rotation!==void 0){let a=e.rotation,l=r;if(r===l)if(typeof a=="number"&&isFinite(a)){if(a>360||isNaN(a))return v.errors=[{instancePath:n+"/rotation",schemaPath:"#/properties/rotation/maximum",keyword:"maximum",params:{comparison:"<=",limit:360},message:"must be <= 360"}],!1;if(a<-360||isNaN(a))return v.errors=[{instancePath:n+"/rotation",schemaPath:"#/properties/rotation/minimum",keyword:"minimum",params:{comparison:">=",limit:-360},message:"must be >= -360"}],!1}else return v.errors=[{instancePath:n+"/rotation",schemaPath:"#/properties/rotation/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var p=l===r}else var p=!0;if(p){if(e.defaultYaw!==void 0){let a=e.defaultYaw,l=r;if(r===l)if(typeof a=="number"&&isFinite(a)){if(a>360||isNaN(a))return v.errors=[{instancePath:n+"/defaultYaw",schemaPath:"#/properties/defaultYaw/maximum",keyword:"maximum",params:{comparison:"<=",limit:360},message:"must be <= 360"}],!1;if(a<-360||isNaN(a))return v.errors=[{instancePath:n+"/defaultYaw",schemaPath:"#/properties/defaultYaw/minimum",keyword:"minimum",params:{comparison:">=",limit:-360},message:"must be >= -360"}],!1}else return v.errors=[{instancePath:n+"/defaultYaw",schemaPath:"#/properties/defaultYaw/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var p=l===r}else var p=!0;if(p){if(e.defaultTilt!==void 0){let a=e.defaultTilt,l=r;if(r===l)if(typeof a=="number"&&isFinite(a)){if(a>30||isNaN(a))return v.errors=[{instancePath:n+"/defaultTilt",schemaPath:"#/properties/defaultTilt/maximum",keyword:"maximum",params:{comparison:"<=",limit:30},message:"must be <= 30"}],!1;if(a<-90||isNaN(a))return v.errors=[{instancePath:n+"/defaultTilt",schemaPath:"#/properties/defaultTilt/minimum",keyword:"minimum",params:{comparison:">=",limit:-90},message:"must be >= -90"}],!1}else return v.errors=[{instancePath:n+"/defaultTilt",schemaPath:"#/properties/defaultTilt/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var p=l===r}else var p=!0;if(p)if(e.defaultViewFov!==void 0){let a=e.defaultViewFov,l=r;if(r===l)if(typeof a=="number"&&isFinite(a)){if(a>120||isNaN(a))return v.errors=[{instancePath:n+"/defaultViewFov",schemaPath:"#/properties/defaultViewFov/maximum",keyword:"maximum",params:{comparison:"<=",limit:120},message:"must be <= 120"}],!1;if(a<20||isNaN(a))return v.errors=[{instancePath:n+"/defaultViewFov",schemaPath:"#/properties/defaultViewFov/minimum",keyword:"minimum",params:{comparison:">=",limit:20},message:"must be >= 20"}],!1}else return v.errors=[{instancePath:n+"/defaultViewFov",schemaPath:"#/properties/defaultViewFov/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var p=l===r}else var p=!0}}}}}}}}}}else return v.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return v.errors=t,r===0}v.evaluated={props:{version:!0,projection:!0,mount:!0,centerX:!0,centerY:!0,radius:!0,fov:!0,rotation:!0,defaultYaw:!0,defaultTilt:!0,defaultViewFov:!0},dynamicProps:!1,dynamicItems:!1};function S(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=S.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(typeof e=="string"){if(x(e)>1023)return S.errors=[{instancePath:n,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:1023},message:"must NOT have more than 1023 characters"}],!1;if(new TextEncoder().encode(e).byteLength>1023)return S.errors=[{instancePath:n,schemaPath:"#/x-lightnvr-max-bytes",keyword:"x-lightnvr-max-bytes",params:{},message:"must fit the UTF-8 byte limit"}],!1;if(e!==""){let y,a=!0;try{y=JSON.parse(e)}catch{a=!1}if(a){let l=r;v(y,{instancePath:n,parentData:e,parentDataProperty:g,rootData:c,dynamicAnchors:d})||(t=t===null?v.errors:t.concat(v.errors),r=t.length);var p=l===r;a=p}if(!a)return S.errors=[{instancePath:n,schemaPath:"#/contentSchema",keyword:"contentSchema",params:{},message:"must contain valid JSON matching contentSchema (or be empty)"}],!1}}else return S.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return S.errors=t,r===0}S.evaluated={dynamicProps:!1,dynamicItems:!1};function h(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=h.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),!(typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)))return h.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;if(r===0&&typeof e=="number"&&isFinite(e)){if(e>2147483647||isNaN(e))return h.errors=[{instancePath:n,schemaPath:"#/maximum",keyword:"maximum",params:{comparison:"<=",limit:2147483647},message:"must be <= 2147483647"}],!1;if(e<-2147483648||isNaN(e))return h.errors=[{instancePath:n,schemaPath:"#/minimum",keyword:"minimum",params:{comparison:">=",limit:-2147483648},message:"must be >= -2147483648"}],!1}return h.errors=t,r===0}h.evaluated={dynamicProps:!1,dynamicItems:!1};var Q={type:"integer",enum:[0,1],$id:"urn:lightnvr:contract:StreamProtocol"};function E(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=E.evaluated;return o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)?e===0||e===1?(E.errors=t,r===0):(E.errors=[{instancePath:n,schemaPath:"#/enum",keyword:"enum",params:{allowedValues:Q.enum},message:"must be equal to one of the allowed values"}],!1):(E.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1)}E.evaluated={dynamicProps:!1,dynamicItems:!1};function L(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=L.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),!(typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)))return L.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;if(r===0&&typeof e=="number"&&isFinite(e)){if(e>365||isNaN(e))return L.errors=[{instancePath:n,schemaPath:"#/maximum",keyword:"maximum",params:{comparison:"<=",limit:365},message:"must be <= 365"}],!1;if(e<-1||isNaN(e))return L.errors=[{instancePath:n,schemaPath:"#/minimum",keyword:"minimum",params:{comparison:">=",limit:-1},message:"must be >= -1"}],!1}return L.errors=t,r===0}L.evaluated={dynamicProps:!1,dynamicItems:!1};function D(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=D.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(Array.isArray(e)){if(e.length>168)return D.errors=[{instancePath:n,schemaPath:"#/maxItems",keyword:"maxItems",params:{limit:168},message:"must NOT have more than 168 items"}],!1;if(e.length<168)return D.errors=[{instancePath:n,schemaPath:"#/minItems",keyword:"minItems",params:{limit:168},message:"must NOT have fewer than 168 items"}],!1;{var p=!0;let y=e.length;for(let a=0;a511)return _.errors=[{instancePath:n+"/url",schemaPath:"#/properties/url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return _.errors=[{instancePath:n+"/url",schemaPath:"#/properties/url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=f===r}else var i=!0;if(i){if(e.enabled!==void 0){let s=r;if(typeof e.enabled!="boolean")return _.errors=[{instancePath:n+"/enabled",schemaPath:"#/properties/enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.streaming_enabled!==void 0){let s=r;if(typeof e.streaming_enabled!="boolean")return _.errors=[{instancePath:n+"/streaming_enabled",schemaPath:"#/properties/streaming_enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.playback_transport!==void 0){let s=r;T(e.playback_transport,{instancePath:n+"/playback_transport",parentData:e,parentDataProperty:"playback_transport",rootData:c,dynamicAnchors:d})||(t=t===null?T.errors:t.concat(T.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.eptz_config!==void 0){let s=r;S(e.eptz_config,{instancePath:n+"/eptz_config",parentData:e,parentDataProperty:"eptz_config",rootData:c,dynamicAnchors:d})||(t=t===null?S.errors:t.concat(S.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.width!==void 0){let s=r;h(e.width,{instancePath:n+"/width",parentData:e,parentDataProperty:"width",rootData:c,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.height!==void 0){let s=r;h(e.height,{instancePath:n+"/height",parentData:e,parentDataProperty:"height",rootData:c,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.fps!==void 0){let s=r;h(e.fps,{instancePath:n+"/fps",parentData:e,parentDataProperty:"fps",rootData:c,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.codec!==void 0){let s=e.codec,f=r;if(r===f)if(typeof s=="string"){if(x(s)>15)return _.errors=[{instancePath:n+"/codec",schemaPath:"#/properties/codec/maxLength",keyword:"maxLength",params:{limit:15},message:"must NOT have more than 15 characters"}],!1}else return _.errors=[{instancePath:n+"/codec",schemaPath:"#/properties/codec/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=f===r}else var i=!0;if(i){if(e.priority!==void 0){let s=r;h(e.priority,{instancePath:n+"/priority",parentData:e,parentDataProperty:"priority",rootData:c,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.record!==void 0){let s=r;if(typeof e.record!="boolean")return _.errors=[{instancePath:n+"/record",schemaPath:"#/properties/record/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.segment_duration!==void 0){let s=r;h(e.segment_duration,{instancePath:n+"/segment_duration",parentData:e,parentDataProperty:"segment_duration",rootData:c,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.detection_based_recording!==void 0){let s=r;if(typeof e.detection_based_recording!="boolean")return _.errors=[{instancePath:n+"/detection_based_recording",schemaPath:"#/properties/detection_based_recording/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.detection_model!==void 0){let s=e.detection_model,f=r;if(r===f)if(typeof s=="string"){if(x(s)>511)return _.errors=[{instancePath:n+"/detection_model",schemaPath:"#/properties/detection_model/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return _.errors=[{instancePath:n+"/detection_model",schemaPath:"#/properties/detection_model/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=f===r}else var i=!0;if(i){if(e.detection_threshold!==void 0){let s=e.detection_threshold,f=r;if(!(typeof s=="number"&&!(s%1)&&!isNaN(s)&&isFinite(s)))return _.errors=[{instancePath:n+"/detection_threshold",schemaPath:"#/properties/detection_threshold/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;var i=f===r}else var i=!0;if(i){if(e.detection_interval!==void 0){let s=r;h(e.detection_interval,{instancePath:n+"/detection_interval",parentData:e,parentDataProperty:"detection_interval",rootData:c,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.pre_detection_buffer!==void 0){let s=r;h(e.pre_detection_buffer,{instancePath:n+"/pre_detection_buffer",parentData:e,parentDataProperty:"pre_detection_buffer",rootData:c,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.post_detection_buffer!==void 0){let s=r;h(e.post_detection_buffer,{instancePath:n+"/post_detection_buffer",parentData:e,parentDataProperty:"post_detection_buffer",rootData:c,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.detection_object_filter!==void 0){let s=e.detection_object_filter,f=r;if(r===f)if(typeof s=="string"){if(x(s)>15)return _.errors=[{instancePath:n+"/detection_object_filter",schemaPath:"#/properties/detection_object_filter/maxLength",keyword:"maxLength",params:{limit:15},message:"must NOT have more than 15 characters"}],!1}else return _.errors=[{instancePath:n+"/detection_object_filter",schemaPath:"#/properties/detection_object_filter/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=f===r}else var i=!0;if(i){if(e.detection_object_filter_list!==void 0){let s=e.detection_object_filter_list,f=r;if(r===f)if(typeof s=="string"){if(x(s)>255)return _.errors=[{instancePath:n+"/detection_object_filter_list",schemaPath:"#/properties/detection_object_filter_list/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1}else return _.errors=[{instancePath:n+"/detection_object_filter_list",schemaPath:"#/properties/detection_object_filter_list/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=f===r}else var i=!0;if(i){if(e.protocol!==void 0){let s=r;E(e.protocol,{instancePath:n+"/protocol",parentData:e,parentDataProperty:"protocol",rootData:c,dynamicAnchors:d})||(t=t===null?E.errors:t.concat(E.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.record_audio!==void 0){let s=r;if(typeof e.record_audio!="boolean")return _.errors=[{instancePath:n+"/record_audio",schemaPath:"#/properties/record_audio/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.audio_voice_enhancement!==void 0){let s=r;if(typeof e.audio_voice_enhancement!="boolean")return _.errors=[{instancePath:n+"/audio_voice_enhancement",schemaPath:"#/properties/audio_voice_enhancement/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.isOnvif!==void 0){let s=r;if(typeof e.isOnvif!="boolean")return _.errors=[{instancePath:n+"/isOnvif",schemaPath:"#/properties/isOnvif/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.backchannel_enabled!==void 0){let s=r;if(typeof e.backchannel_enabled!="boolean")return _.errors=[{instancePath:n+"/backchannel_enabled",schemaPath:"#/properties/backchannel_enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.retention_days!==void 0){let s=r;L(e.retention_days,{instancePath:n+"/retention_days",parentData:e,parentDataProperty:"retention_days",rootData:c,dynamicAnchors:d})||(t=t===null?L.errors:t.concat(L.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.detection_retention_days!==void 0){let s=r;L(e.detection_retention_days,{instancePath:n+"/detection_retention_days",parentData:e,parentDataProperty:"detection_retention_days",rootData:c,dynamicAnchors:d})||(t=t===null?L.errors:t.concat(L.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.max_storage_mb!==void 0){let s=r;h(e.max_storage_mb,{instancePath:n+"/max_storage_mb",parentData:e,parentDataProperty:"max_storage_mb",rootData:c,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.tier_critical_multiplier!==void 0){let s=e.tier_critical_multiplier,f=r;if(!(typeof s=="number"&&isFinite(s)))return _.errors=[{instancePath:n+"/tier_critical_multiplier",schemaPath:"#/properties/tier_critical_multiplier/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var i=f===r}else var i=!0;if(i){if(e.tier_important_multiplier!==void 0){let s=e.tier_important_multiplier,f=r;if(!(typeof s=="number"&&isFinite(s)))return _.errors=[{instancePath:n+"/tier_important_multiplier",schemaPath:"#/properties/tier_important_multiplier/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var i=f===r}else var i=!0;if(i){if(e.tier_ephemeral_multiplier!==void 0){let s=e.tier_ephemeral_multiplier,f=r;if(!(typeof s=="number"&&isFinite(s)))return _.errors=[{instancePath:n+"/tier_ephemeral_multiplier",schemaPath:"#/properties/tier_ephemeral_multiplier/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var i=f===r}else var i=!0;if(i){if(e.storage_priority!==void 0){let s=r;h(e.storage_priority,{instancePath:n+"/storage_priority",parentData:e,parentDataProperty:"storage_priority",rootData:c,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.ptz_enabled!==void 0){let s=r;if(typeof e.ptz_enabled!="boolean")return _.errors=[{instancePath:n+"/ptz_enabled",schemaPath:"#/properties/ptz_enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.ptz_max_x!==void 0){let s=r;h(e.ptz_max_x,{instancePath:n+"/ptz_max_x",parentData:e,parentDataProperty:"ptz_max_x",rootData:c,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.ptz_max_y!==void 0){let s=r;h(e.ptz_max_y,{instancePath:n+"/ptz_max_y",parentData:e,parentDataProperty:"ptz_max_y",rootData:c,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.ptz_max_z!==void 0){let s=r;h(e.ptz_max_z,{instancePath:n+"/ptz_max_z",parentData:e,parentDataProperty:"ptz_max_z",rootData:c,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.ptz_has_home!==void 0){let s=r;if(typeof e.ptz_has_home!="boolean")return _.errors=[{instancePath:n+"/ptz_has_home",schemaPath:"#/properties/ptz_has_home/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.onvif_username!==void 0){let s=e.onvif_username,f=r;if(r===f)if(typeof s=="string"){if(x(s)>63)return _.errors=[{instancePath:n+"/onvif_username",schemaPath:"#/properties/onvif_username/maxLength",keyword:"maxLength",params:{limit:63},message:"must NOT have more than 63 characters"}],!1}else return _.errors=[{instancePath:n+"/onvif_username",schemaPath:"#/properties/onvif_username/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=f===r}else var i=!0;if(i){if(e.onvif_password!==void 0){let s=e.onvif_password,f=r;if(r===f)if(typeof s=="string"){if(x(s)>63)return _.errors=[{instancePath:n+"/onvif_password",schemaPath:"#/properties/onvif_password/maxLength",keyword:"maxLength",params:{limit:63},message:"must NOT have more than 63 characters"}],!1}else return _.errors=[{instancePath:n+"/onvif_password",schemaPath:"#/properties/onvif_password/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=f===r}else var i=!0;if(i){if(e.onvif_profile!==void 0){let s=e.onvif_profile,f=r;if(r===f)if(typeof s=="string"){if(x(s)>63)return _.errors=[{instancePath:n+"/onvif_profile",schemaPath:"#/properties/onvif_profile/maxLength",keyword:"maxLength",params:{limit:63},message:"must NOT have more than 63 characters"}],!1}else return _.errors=[{instancePath:n+"/onvif_profile",schemaPath:"#/properties/onvif_profile/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=f===r}else var i=!0;if(i){if(e.onvif_port!==void 0){let s=r;h(e.onvif_port,{instancePath:n+"/onvif_port",parentData:e,parentDataProperty:"onvif_port",rootData:c,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.record_on_schedule!==void 0){let s=r;if(typeof e.record_on_schedule!="boolean")return _.errors=[{instancePath:n+"/record_on_schedule",schemaPath:"#/properties/record_on_schedule/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.recording_schedule!==void 0){let s=r;D(e.recording_schedule,{instancePath:n+"/recording_schedule",parentData:e,parentDataProperty:"recording_schedule",rootData:c,dynamicAnchors:d})||(t=t===null?D.errors:t.concat(D.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.detection_record_on_schedule!==void 0){let s=r;if(typeof e.detection_record_on_schedule!="boolean")return _.errors=[{instancePath:n+"/detection_record_on_schedule",schemaPath:"#/properties/detection_record_on_schedule/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.detection_recording_schedule!==void 0){let s=r;D(e.detection_recording_schedule,{instancePath:n+"/detection_recording_schedule",parentData:e,parentDataProperty:"detection_recording_schedule",rootData:c,dynamicAnchors:d})||(t=t===null?D.errors:t.concat(D.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.tags!==void 0){let s=e.tags,f=r;if(r===f)if(typeof s=="string"){if(x(s)>255)return _.errors=[{instancePath:n+"/tags",schemaPath:"#/properties/tags/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1}else return _.errors=[{instancePath:n+"/tags",schemaPath:"#/properties/tags/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=f===r}else var i=!0;if(i){if(e.admin_url!==void 0){let s=e.admin_url,f=r;if(r===f)if(typeof s=="string"){if(x(s)>511)return _.errors=[{instancePath:n+"/admin_url",schemaPath:"#/properties/admin_url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return _.errors=[{instancePath:n+"/admin_url",schemaPath:"#/properties/admin_url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=f===r}else var i=!0;if(i){if(e.privacy_mode!==void 0){let s=r;if(typeof e.privacy_mode!="boolean")return _.errors=[{instancePath:n+"/privacy_mode",schemaPath:"#/properties/privacy_mode/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.can_control_privacy!==void 0){let s=r;if(typeof e.can_control_privacy!="boolean")return _.errors=[{instancePath:n+"/can_control_privacy",schemaPath:"#/properties/can_control_privacy/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.motion_trigger_source!==void 0){let s=e.motion_trigger_source,f=r;if(r===f)if(typeof s=="string"){if(x(s)>255)return _.errors=[{instancePath:n+"/motion_trigger_source",schemaPath:"#/properties/motion_trigger_source/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1}else return _.errors=[{instancePath:n+"/motion_trigger_source",schemaPath:"#/properties/motion_trigger_source/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=f===r}else var i=!0;if(i){if(e.go2rtc_source_override!==void 0){let s=e.go2rtc_source_override,f=r;if(r===f)if(typeof s=="string"){if(x(s)>8191)return _.errors=[{instancePath:n+"/go2rtc_source_override",schemaPath:"#/properties/go2rtc_source_override/maxLength",keyword:"maxLength",params:{limit:8191},message:"must NOT have more than 8191 characters"}],!1}else return _.errors=[{instancePath:n+"/go2rtc_source_override",schemaPath:"#/properties/go2rtc_source_override/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=f===r}else var i=!0;if(i){if(e.sub_stream_url!==void 0){let s=e.sub_stream_url,f=r;if(r===f)if(typeof s=="string"){if(x(s)>511)return _.errors=[{instancePath:n+"/sub_stream_url",schemaPath:"#/properties/sub_stream_url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return _.errors=[{instancePath:n+"/sub_stream_url",schemaPath:"#/properties/sub_stream_url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=f===r}else var i=!0;if(i){if(e.has_sub_stream!==void 0){let s=r;if(typeof e.has_sub_stream!="boolean")return _.errors=[{instancePath:n+"/has_sub_stream",schemaPath:"#/properties/has_sub_stream/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.detection_url!==void 0){let s=e.detection_url,f=r;if(r===f)if(typeof s=="string"){if(x(s)>511)return _.errors=[{instancePath:n+"/detection_url",schemaPath:"#/properties/detection_url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return _.errors=[{instancePath:n+"/detection_url",schemaPath:"#/properties/detection_url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=f===r}else var i=!0;if(i){if(e.publish_url!==void 0){let s=e.publish_url,f=r;if(r===f)if(typeof s=="string"){if(x(s)>511)return _.errors=[{instancePath:n+"/publish_url",schemaPath:"#/properties/publish_url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return _.errors=[{instancePath:n+"/publish_url",schemaPath:"#/properties/publish_url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=f===r}else var i=!0;if(i){if(e.status!==void 0){let s=r;A(e.status,{instancePath:n+"/status",parentData:e,parentDataProperty:"status",rootData:c,dynamicAnchors:d})||(t=t===null?A.errors:t.concat(A.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.error_message!==void 0){let s=e.error_message,f=r;if(r===f)if(typeof s=="string"){if(x(s)>255)return _.errors=[{instancePath:n+"/error_message",schemaPath:"#/properties/error_message/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1;if(x(s)<1)return _.errors=[{instancePath:n+"/error_message",schemaPath:"#/properties/error_message/minLength",keyword:"minLength",params:{limit:1},message:"must NOT have fewer than 1 characters"}],!1}else return _.errors=[{instancePath:n+"/error_message",schemaPath:"#/properties/error_message/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=f===r}else var i=!0;if(i)if(e.go2rtc_hls_available!==void 0){let s=r;if(typeof e.go2rtc_hls_available!="boolean")return _.errors=[{instancePath:n+"/go2rtc_hls_available",schemaPath:"#/properties/go2rtc_hls_available/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}else return _.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return _.errors=t,r===0}_.evaluated={props:{camera_uuid:!0,location_uuid:!0,name:!0,url:!0,enabled:!0,streaming_enabled:!0,playback_transport:!0,eptz_config:!0,width:!0,height:!0,fps:!0,codec:!0,priority:!0,record:!0,segment_duration:!0,detection_based_recording:!0,detection_model:!0,detection_threshold:!0,detection_interval:!0,pre_detection_buffer:!0,post_detection_buffer:!0,detection_object_filter:!0,detection_object_filter_list:!0,protocol:!0,record_audio:!0,audio_voice_enhancement:!0,isOnvif:!0,backchannel_enabled:!0,retention_days:!0,detection_retention_days:!0,max_storage_mb:!0,tier_critical_multiplier:!0,tier_important_multiplier:!0,tier_ephemeral_multiplier:!0,storage_priority:!0,ptz_enabled:!0,ptz_max_x:!0,ptz_max_y:!0,ptz_max_z:!0,ptz_has_home:!0,onvif_username:!0,onvif_password:!0,onvif_profile:!0,onvif_port:!0,record_on_schedule:!0,recording_schedule:!0,detection_record_on_schedule:!0,detection_recording_schedule:!0,tags:!0,admin_url:!0,privacy_mode:!0,can_control_privacy:!0,motion_trigger_source:!0,go2rtc_source_override:!0,sub_stream_url:!0,has_sub_stream:!0,detection_url:!0,publish_url:!0,status:!0,error_message:!0,go2rtc_hls_available:!0},dynamicProps:!1,dynamicItems:!1};var ee=new RegExp("^(?![A-Za-z][A-Za-z0-9+.-]*://[^/?#]*@)","u");function O(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=O.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(typeof e=="string"){if(x(e)>511)return O.errors=[{instancePath:n,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1;if(!ee.test(e))return O.errors=[{instancePath:n,schemaPath:"#/pattern",keyword:"pattern",params:{pattern:"^(?![A-Za-z][A-Za-z0-9+.-]*://[^/?#]*@)"},message:'must match pattern "^(?![A-Za-z][A-Za-z0-9+.-]*://[^/?#]*@)"'}],!1}else return O.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return O.errors=t,r===0}O.evaluated={dynamicProps:!1,dynamicItems:!1};function $(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=$.evaluated;o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0);let p=r;_(e,{instancePath:n,parentData:b,parentDataProperty:g,rootData:c,dynamicAnchors:d})||(t=t===null?_.errors:t.concat(_.errors),r=t.length);var y=p===r;if(y){let j=r,i=r,m=!0,s=r,f=r,z=!1,R=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let P;if(e.isOnvif===void 0&&(P="isOnvif")){let k={};t===null?t=[k]:t.push(k),r++}else if(e.isOnvif!==void 0&&e.isOnvif!==!0){let k={};t===null?t=[k]:t.push(k),r++}}var l=R===r;if(z=z||l,l){var a={};a.isOnvif=!0}let X=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let P;if(e.onvif_username===void 0&&(P="onvif_username")){let k={};t===null?t=[k]:t.push(k),r++}else if(e.onvif_username!==void 0){let k=e.onvif_username;if(typeof k=="string"&&x(k)<1){let U={};t===null?t=[U]:t.push(U),r++}}}var l=X===r;z=z||l,l&&a!==!0&&(a=a||{},a.onvif_username=!0);let B=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let P;if(e.onvif_password===void 0&&(P="onvif_password")){let k={};t===null?t=[k]:t.push(k),r++}else if(e.onvif_password!==void 0){let k=e.onvif_password;if(typeof k=="string"&&x(k)<1){let U={};t===null?t=[U]:t.push(U),r++}}}var l=B===r;if(z=z||l,l&&a!==!0&&(a=a||{},a.onvif_password=!0),z)r=f,t!==null&&(f?t.length=f:t=null);else{let P={};t===null?t=[P]:t.push(P),r++}var u=s===r;if(r=i,t!==null&&(i?t.length=i:t=null),u){let P=r;e&&typeof e=="object"&&!Array.isArray(e)&&e.url!==void 0&&(O(e.url,{instancePath:n+"/url",parentData:e,parentDataProperty:"url",rootData:c,dynamicAnchors:d})||(t=t===null?O.errors:t.concat(O.errors),r=t.length));var u=P===r;m=u,m&&a!==!0&&(a=a||{},a.url=!0)}if(!m){let P={instancePath:n,schemaPath:"#/allOf/1/if",keyword:"if",params:{failingKeyword:"then"},message:'must match "then" schema'};return t===null?t=[P]:t.push(P),r++,$.errors=t,!1}if(r===j)if(e&&typeof e=="object"&&!Array.isArray(e)){let P;if(e.url===void 0&&(P="url")||e.onvif_username===void 0&&(P="onvif_username")||e.onvif_password===void 0&&(P="onvif_password")||e.admin_url===void 0&&(P="admin_url")||e.go2rtc_source_override===void 0&&(P="go2rtc_source_override")||e.sub_stream_url===void 0&&(P="sub_stream_url")||e.detection_url===void 0&&(P="detection_url")||e.publish_url===void 0&&(P="publish_url"))return $.errors=[{instancePath:n,schemaPath:"#/allOf/1/required",keyword:"required",params:{missingProperty:P},message:"must have required property '"+P+"'"}],!1;if(a!==!0&&(a=a||{},a.can_control_privacy=!0),e.can_control_privacy!==void 0&&e.can_control_privacy!==!0)return $.errors=[{instancePath:n+"/can_control_privacy",schemaPath:"#/allOf/1/properties/can_control_privacy/const",keyword:"const",params:{allowedValue:!0},message:"must be equal to constant"}],!1}else return $.errors=[{instancePath:n,schemaPath:"#/allOf/1/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;var y=j===r;y&&a!==!0&&(a=a||{},a.camera_uuid=!0,a.location_uuid=!0,a.name=!0,a.url=!0,a.enabled=!0,a.streaming_enabled=!0,a.playback_transport=!0,a.eptz_config=!0,a.width=!0,a.height=!0,a.fps=!0,a.codec=!0,a.priority=!0,a.record=!0,a.segment_duration=!0,a.detection_based_recording=!0,a.detection_model=!0,a.detection_threshold=!0,a.detection_interval=!0,a.pre_detection_buffer=!0,a.post_detection_buffer=!0,a.detection_object_filter=!0,a.detection_object_filter_list=!0,a.protocol=!0,a.record_audio=!0,a.audio_voice_enhancement=!0,a.isOnvif=!0,a.backchannel_enabled=!0,a.retention_days=!0,a.detection_retention_days=!0,a.max_storage_mb=!0,a.tier_critical_multiplier=!0,a.tier_important_multiplier=!0,a.tier_ephemeral_multiplier=!0,a.storage_priority=!0,a.ptz_enabled=!0,a.ptz_max_x=!0,a.ptz_max_y=!0,a.ptz_max_z=!0,a.ptz_has_home=!0,a.onvif_username=!0,a.onvif_password=!0,a.onvif_profile=!0,a.onvif_port=!0,a.record_on_schedule=!0,a.recording_schedule=!0,a.detection_record_on_schedule=!0,a.detection_recording_schedule=!0,a.tags=!0,a.admin_url=!0,a.privacy_mode=!0,a.can_control_privacy=!0,a.motion_trigger_source=!0,a.go2rtc_source_override=!0,a.sub_stream_url=!0,a.has_sub_stream=!0,a.detection_url=!0,a.publish_url=!0,a.status=!0,a.error_message=!0,a.go2rtc_hls_available=!0)}return $.errors=t,o.props=a,r===0}$.evaluated={dynamicProps:!0,dynamicItems:!1};var re=new RegExp("[?&;][^=&#]*([Pp][Aa][Ss][Ss]|[Pp][Ww][Dd]|[Tt][Oo][Kk][Ee][Nn]|[Ss][Ee][Cc][Rr][Ee][Tt]|[Aa][Pp][Ii]_?[Kk][Ee][Yy])[^=&#]*=[^&#]","u");function q(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=q.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e!="string")return q.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;let p=r,y=r;if(typeof e=="string"&&!re.test(e)){let l={};t===null?t=[l]:t.push(l),r++}var a=y===r;return a?(q.errors=[{instancePath:n,schemaPath:"#/not",keyword:"not",params:{},message:"must NOT be valid"}],!1):(r=p,t!==null&&(p?t.length=p:t=null),q.errors=t,r===0)}q.evaluated={dynamicProps:!1,dynamicItems:!1};function V(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=V.evaluated;o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0);let p=r;O(e,{instancePath:n,parentData:b,parentDataProperty:g,rootData:c,dynamicAnchors:d})||(t=t===null?O.errors:t.concat(O.errors),r=t.length);var y=p===r;if(y){let a=r;q(e,{instancePath:n,parentData:b,parentDataProperty:g,rootData:c,dynamicAnchors:d})||(t=t===null?q.errors:t.concat(q.errors),r=t.length);var y=a===r}return V.errors=t,r===0}V.evaluated={dynamicProps:!1,dynamicItems:!1};function w(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=w.evaluated;return o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e!="string"?(w.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1):e!==""?(w.errors=[{instancePath:n,schemaPath:"#/const",keyword:"const",params:{allowedValue:""},message:"must be equal to constant"}],!1):(w.errors=t,r===0)}w.evaluated={dynamicProps:!1,dynamicItems:!1};function F(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=F.evaluated;o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0);let p=r;_(e,{instancePath:n,parentData:b,parentDataProperty:g,rootData:c,dynamicAnchors:d})||(t=t===null?_.errors:t.concat(_.errors),r=t.length);var y=p===r;if(y){let u=r,j=r,i=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let s;if(e.detection_api_url===void 0&&(s="detection_api_url")){let f={};t===null?t=[f]:t.push(f),r++}}var a=i===r;if(a)return F.errors=[{instancePath:n,schemaPath:"#/allOf/1/not",keyword:"not",params:{},message:"must NOT be valid"}],!1;if(r=j,t!==null&&(j?t.length=j:t=null),r===u)if(e&&typeof e=="object"&&!Array.isArray(e)){if(e.can_control_privacy!==void 0){let s=r;if(e.can_control_privacy!==!1)return F.errors=[{instancePath:n+"/can_control_privacy",schemaPath:"#/allOf/1/properties/can_control_privacy/const",keyword:"const",params:{allowedValue:!1},message:"must be equal to constant"}],!1;var l=s===r}else var l=!0;if(l){if(e.url!==void 0){let s=r;V(e.url,{instancePath:n+"/url",parentData:e,parentDataProperty:"url",rootData:c,dynamicAnchors:d})||(t=t===null?V.errors:t.concat(V.errors),r=t.length);var l=s===r}else var l=!0;if(l){if(e.onvif_username!==void 0){let s=r;w(e.onvif_username,{instancePath:n+"/onvif_username",parentData:e,parentDataProperty:"onvif_username",rootData:c,dynamicAnchors:d})||(t=t===null?w.errors:t.concat(w.errors),r=t.length);var l=s===r}else var l=!0;if(l){if(e.onvif_password!==void 0){let s=r;w(e.onvif_password,{instancePath:n+"/onvif_password",parentData:e,parentDataProperty:"onvif_password",rootData:c,dynamicAnchors:d})||(t=t===null?w.errors:t.concat(w.errors),r=t.length);var l=s===r}else var l=!0;if(l){if(e.admin_url!==void 0){let s=r;w(e.admin_url,{instancePath:n+"/admin_url",parentData:e,parentDataProperty:"admin_url",rootData:c,dynamicAnchors:d})||(t=t===null?w.errors:t.concat(w.errors),r=t.length);var l=s===r}else var l=!0;if(l){if(e.go2rtc_source_override!==void 0){let s=r;w(e.go2rtc_source_override,{instancePath:n+"/go2rtc_source_override",parentData:e,parentDataProperty:"go2rtc_source_override",rootData:c,dynamicAnchors:d})||(t=t===null?w.errors:t.concat(w.errors),r=t.length);var l=s===r}else var l=!0;if(l){if(e.sub_stream_url!==void 0){let s=r;w(e.sub_stream_url,{instancePath:n+"/sub_stream_url",parentData:e,parentDataProperty:"sub_stream_url",rootData:c,dynamicAnchors:d})||(t=t===null?w.errors:t.concat(w.errors),r=t.length);var l=s===r}else var l=!0;if(l){if(e.detection_url!==void 0){let s=r;w(e.detection_url,{instancePath:n+"/detection_url",parentData:e,parentDataProperty:"detection_url",rootData:c,dynamicAnchors:d})||(t=t===null?w.errors:t.concat(w.errors),r=t.length);var l=s===r}else var l=!0;if(l)if(e.publish_url!==void 0){let s=r;w(e.publish_url,{instancePath:n+"/publish_url",parentData:e,parentDataProperty:"publish_url",rootData:c,dynamicAnchors:d})||(t=t===null?w.errors:t.concat(w.errors),r=t.length);var l=s===r}else var l=!0}}}}}}}}else return F.errors=[{instancePath:n,schemaPath:"#/allOf/1/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;var y=u===r}return F.errors=t,r===0}F.evaluated={props:{can_control_privacy:!0,url:!0,onvif_username:!0,onvif_password:!0,admin_url:!0,go2rtc_source_override:!0,sub_stream_url:!0,detection_url:!0,publish_url:!0,camera_uuid:!0,location_uuid:!0,name:!0,enabled:!0,streaming_enabled:!0,playback_transport:!0,eptz_config:!0,width:!0,height:!0,fps:!0,codec:!0,priority:!0,record:!0,segment_duration:!0,detection_based_recording:!0,detection_model:!0,detection_threshold:!0,detection_interval:!0,pre_detection_buffer:!0,post_detection_buffer:!0,detection_object_filter:!0,detection_object_filter_list:!0,protocol:!0,record_audio:!0,audio_voice_enhancement:!0,isOnvif:!0,backchannel_enabled:!0,retention_days:!0,detection_retention_days:!0,max_storage_mb:!0,tier_critical_multiplier:!0,tier_important_multiplier:!0,tier_ephemeral_multiplier:!0,storage_priority:!0,ptz_enabled:!0,ptz_max_x:!0,ptz_max_y:!0,ptz_max_z:!0,ptz_has_home:!0,onvif_profile:!0,onvif_port:!0,record_on_schedule:!0,recording_schedule:!0,detection_record_on_schedule:!0,detection_recording_schedule:!0,tags:!0,privacy_mode:!0,motion_trigger_source:!0,has_sub_stream:!0,status:!0,error_message:!0,go2rtc_hls_available:!0},dynamicProps:!1,dynamicItems:!1};function Y(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=Y.evaluated;o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0);let p=r,y=!1,a=null,l=r;if(!$(e,{instancePath:n,parentData:b,parentDataProperty:g,rootData:c,dynamicAnchors:d}))t=t===null?$.errors:t.concat($.errors),r=t.length;else var u=$.evaluated.props;var i=l===r;i&&(y=!0,a=0);let j=r;F(e,{instancePath:n,parentData:b,parentDataProperty:g,rootData:c,dynamicAnchors:d})||(t=t===null?F.errors:t.concat(F.errors),r=t.length);var i=j===r;if(i&&y?(y=!1,a=[a,1]):i&&(y=!0,a=1,u!==!0&&(u=u||{},u.can_control_privacy=!0,u.url=!0,u.onvif_username=!0,u.onvif_password=!0,u.admin_url=!0,u.go2rtc_source_override=!0,u.sub_stream_url=!0,u.detection_url=!0,u.publish_url=!0,u.camera_uuid=!0,u.location_uuid=!0,u.name=!0,u.enabled=!0,u.streaming_enabled=!0,u.playback_transport=!0,u.eptz_config=!0,u.width=!0,u.height=!0,u.fps=!0,u.codec=!0,u.priority=!0,u.record=!0,u.segment_duration=!0,u.detection_based_recording=!0,u.detection_model=!0,u.detection_threshold=!0,u.detection_interval=!0,u.pre_detection_buffer=!0,u.post_detection_buffer=!0,u.detection_object_filter=!0,u.detection_object_filter_list=!0,u.protocol=!0,u.record_audio=!0,u.audio_voice_enhancement=!0,u.isOnvif=!0,u.backchannel_enabled=!0,u.retention_days=!0,u.detection_retention_days=!0,u.max_storage_mb=!0,u.tier_critical_multiplier=!0,u.tier_important_multiplier=!0,u.tier_ephemeral_multiplier=!0,u.storage_priority=!0,u.ptz_enabled=!0,u.ptz_max_x=!0,u.ptz_max_y=!0,u.ptz_max_z=!0,u.ptz_has_home=!0,u.onvif_profile=!0,u.onvif_port=!0,u.record_on_schedule=!0,u.recording_schedule=!0,u.detection_record_on_schedule=!0,u.detection_recording_schedule=!0,u.tags=!0,u.privacy_mode=!0,u.motion_trigger_source=!0,u.has_sub_stream=!0,u.status=!0,u.error_message=!0,u.go2rtc_hls_available=!0)),y)r=p,t!==null&&(p?t.length=p:t=null);else{let m={instancePath:n,schemaPath:"#/oneOf",keyword:"oneOf",params:{passingSchemas:a},message:"must match exactly one schema in oneOf"};return t===null?t=[m]:t.push(m),r++,Y.errors=t,!1}return Y.errors=t,o.props=u,r===0}Y.evaluated={dynamicProps:!0,dynamicItems:!1};function C(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=C.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(Array.isArray(e)){var p=!0;let y=e.length;for(let a=0;a()=>{try{return t||e((t={exports:{}}).exports,t),t.exports}catch(a){throw t=0,a}};var u=o(i=>{"use strict";Object.defineProperty(i,"__esModule",{value:!0});function f(e){let t=e.length,a=0,s=0,n;for(;s=55296&&n<=56319&&s511)return r.errors=[{instancePath:t,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1;if(h.test(e)){if(new TextEncoder().encode(e).byteLength>511)return r.errors=[{instancePath:t,schemaPath:"#/x-lightnvr-max-bytes",keyword:"x-lightnvr-max-bytes",params:{},message:"must fit the UTF-8 byte limit"}],!1}else return r.errors=[{instancePath:t,schemaPath:"#/pattern",keyword:"pattern",params:{pattern:"^[\\t\\n\\v\\f\\r ,]*(([Rr][Tt][Mm][Pp][Ss]?://[!#-&(-+\\x2D-\\x5B\\x5D-~]+)([\\t\\n\\v\\f\\r ,]+[Rr][Tt][Mm][Pp][Ss]?://[!#-&(-+\\x2D-\\x5B\\x5D-~]+)*)?[\\t\\n\\v\\f\\r ,]*$"},message:'must match pattern "^[\\t\\n\\v\\f\\r ,]*(([Rr][Tt][Mm][Pp][Ss]?://[!#-&(-+\\x2D-\\x5B\\x5D-~]+)([\\t\\n\\v\\f\\r ,]+[Rr][Tt][Mm][Pp][Ss]?://[!#-&(-+\\x2D-\\x5B\\x5D-~]+)*)?[\\t\\n\\v\\f\\r ,]*$"'}],!1}return r.errors=x,m===0}r.evaluated={dynamicProps:!1,dynamicItems:!1};export{g as validatePublishUrlList}; diff --git a/web/js/api/generated/validators/RetentionDays.d.mts b/web/js/api/generated/validators/RetentionDays.d.mts new file mode 100644 index 000000000..1a424d1fc --- /dev/null +++ b/web/js/api/generated/validators/RetentionDays.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateRetentionDays } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/RetentionDays.mjs b/web/js/api/generated/validators/RetentionDays.mjs new file mode 100644 index 000000000..021643fe4 --- /dev/null +++ b/web/js/api/generated/validators/RetentionDays.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var p=r;function r(e,{instancePath:s="",parentData:n,parentDataProperty:a,rootData:o=e,dynamicAnchors:u={}}={}){let t=null,m=0,i=r.evaluated;if(i.dynamicProps&&(i.props=void 0),i.dynamicItems&&(i.items=void 0),!(typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)))return r.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;if(m===0&&typeof e=="number"&&isFinite(e)){if(e>365||isNaN(e))return r.errors=[{instancePath:s,schemaPath:"#/maximum",keyword:"maximum",params:{comparison:"<=",limit:365},message:"must be <= 365"}],!1;if(e<-1||isNaN(e))return r.errors=[{instancePath:s,schemaPath:"#/minimum",keyword:"minimum",params:{comparison:">=",limit:-1},message:"must be >= -1"}],!1}return r.errors=t,m===0}r.evaluated={dynamicProps:!1,dynamicItems:!1};export{p as validateRetentionDays}; diff --git a/web/js/api/generated/validators/StreamDetail.d.mts b/web/js/api/generated/validators/StreamDetail.d.mts new file mode 100644 index 000000000..576215867 --- /dev/null +++ b/web/js/api/generated/validators/StreamDetail.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateStreamDetail } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/StreamDetail.mjs b/web/js/api/generated/validators/StreamDetail.mjs new file mode 100644 index 000000000..05913e8bd --- /dev/null +++ b/web/js/api/generated/validators/StreamDetail.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var B=(e,n)=>()=>{try{return n||e((n={exports:{}}).exports,n),n.exports}catch(b){throw n=0,b}};var Z=B(R=>{"use strict";Object.defineProperty(R,"__esModule",{value:!0});function W(e){let n=e.length,b=0,g=0,_;for(;g=55296&&_<=56319&&g36)return N.errors=[{instancePath:n,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:36},message:"must NOT have more than 36 characters"}],!1;if(!H.test(e))return N.errors=[{instancePath:n,schemaPath:"#/pattern",keyword:"pattern",params:{pattern:"^(|[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})$"},message:'must match pattern "^(|[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})$"'}],!1}else return N.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return N.errors=t,r===0}N.evaluated={dynamicProps:!1,dynamicItems:!1};function I(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:_=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=I.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(typeof e=="string"){if(x(e)>255)return I.errors=[{instancePath:n,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1;if(x(e)<1)return I.errors=[{instancePath:n,schemaPath:"#/minLength",keyword:"minLength",params:{limit:1},message:"must NOT have fewer than 1 characters"}],!1;if(new TextEncoder().encode(e).byteLength>255)return I.errors=[{instancePath:n,schemaPath:"#/x-lightnvr-max-bytes",keyword:"x-lightnvr-max-bytes",params:{},message:"must fit the UTF-8 byte limit"}],!1}else return I.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return I.errors=t,r===0}I.evaluated={dynamicProps:!1,dynamicItems:!1};var J={type:"string",enum:["auto","webrtc_only","mse_only","hls_only","webrtc_then_mse","mse_then_hls"],$id:"urn:lightnvr:contract:PlaybackTransport"};function T(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:_=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=T.evaluated;return o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e!="string"?(T.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1):e==="auto"||e==="webrtc_only"||e==="mse_only"||e==="hls_only"||e==="webrtc_then_mse"||e==="mse_then_hls"?(T.errors=t,r===0):(T.errors=[{instancePath:n,schemaPath:"#/enum",keyword:"enum",params:{allowedValues:J.enum},message:"must be equal to one of the allowed values"}],!1)}T.evaluated={dynamicProps:!1,dynamicItems:!1};function v(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:_=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=v.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(e&&typeof e=="object"&&!Array.isArray(e)){let y;if(e.version===void 0&&(y="version")||e.projection===void 0&&(y="projection")||e.mount===void 0&&(y="mount")||e.centerX===void 0&&(y="centerX")||e.centerY===void 0&&(y="centerY")||e.radius===void 0&&(y="radius")||e.fov===void 0&&(y="fov")||e.rotation===void 0&&(y="rotation")||e.defaultYaw===void 0&&(y="defaultYaw")||e.defaultTilt===void 0&&(y="defaultTilt")||e.defaultViewFov===void 0&&(y="defaultViewFov"))return v.errors=[{instancePath:n,schemaPath:"#/required",keyword:"required",params:{missingProperty:y},message:"must have required property '"+y+"'"}],!1;if(e.version!==void 0){let a=r;if(e.version!==1)return v.errors=[{instancePath:n+"/version",schemaPath:"#/properties/version/const",keyword:"const",params:{allowedValue:1},message:"must be equal to constant"}],!1;var p=a===r}else var p=!0;if(p){if(e.projection!==void 0){let a=r;if(e.projection!=="equidistant")return v.errors=[{instancePath:n+"/projection",schemaPath:"#/properties/projection/const",keyword:"const",params:{allowedValue:"equidistant"},message:"must be equal to constant"}],!1;var p=a===r}else var p=!0;if(p){if(e.mount!==void 0){let a=r;if(e.mount!=="ceiling")return v.errors=[{instancePath:n+"/mount",schemaPath:"#/properties/mount/const",keyword:"const",params:{allowedValue:"ceiling"},message:"must be equal to constant"}],!1;var p=a===r}else var p=!0;if(p){if(e.centerX!==void 0){let a=e.centerX,l=r;if(r===l)if(typeof a=="number"&&isFinite(a)){if(a>1||isNaN(a))return v.errors=[{instancePath:n+"/centerX",schemaPath:"#/properties/centerX/maximum",keyword:"maximum",params:{comparison:"<=",limit:1},message:"must be <= 1"}],!1;if(a<0||isNaN(a))return v.errors=[{instancePath:n+"/centerX",schemaPath:"#/properties/centerX/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1}else return v.errors=[{instancePath:n+"/centerX",schemaPath:"#/properties/centerX/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var p=l===r}else var p=!0;if(p){if(e.centerY!==void 0){let a=e.centerY,l=r;if(r===l)if(typeof a=="number"&&isFinite(a)){if(a>1||isNaN(a))return v.errors=[{instancePath:n+"/centerY",schemaPath:"#/properties/centerY/maximum",keyword:"maximum",params:{comparison:"<=",limit:1},message:"must be <= 1"}],!1;if(a<0||isNaN(a))return v.errors=[{instancePath:n+"/centerY",schemaPath:"#/properties/centerY/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1}else return v.errors=[{instancePath:n+"/centerY",schemaPath:"#/properties/centerY/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var p=l===r}else var p=!0;if(p){if(e.radius!==void 0){let a=e.radius,l=r;if(r===l)if(typeof a=="number"&&isFinite(a)){if(a>1||isNaN(a))return v.errors=[{instancePath:n+"/radius",schemaPath:"#/properties/radius/maximum",keyword:"maximum",params:{comparison:"<=",limit:1},message:"must be <= 1"}],!1;if(a<.05||isNaN(a))return v.errors=[{instancePath:n+"/radius",schemaPath:"#/properties/radius/minimum",keyword:"minimum",params:{comparison:">=",limit:.05},message:"must be >= 0.05"}],!1}else return v.errors=[{instancePath:n+"/radius",schemaPath:"#/properties/radius/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var p=l===r}else var p=!0;if(p){if(e.fov!==void 0){let a=e.fov,l=r;if(r===l)if(typeof a=="number"&&isFinite(a)){if(a>360||isNaN(a))return v.errors=[{instancePath:n+"/fov",schemaPath:"#/properties/fov/maximum",keyword:"maximum",params:{comparison:"<=",limit:360},message:"must be <= 360"}],!1;if(a<160||isNaN(a))return v.errors=[{instancePath:n+"/fov",schemaPath:"#/properties/fov/minimum",keyword:"minimum",params:{comparison:">=",limit:160},message:"must be >= 160"}],!1}else return v.errors=[{instancePath:n+"/fov",schemaPath:"#/properties/fov/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var p=l===r}else var p=!0;if(p){if(e.rotation!==void 0){let a=e.rotation,l=r;if(r===l)if(typeof a=="number"&&isFinite(a)){if(a>360||isNaN(a))return v.errors=[{instancePath:n+"/rotation",schemaPath:"#/properties/rotation/maximum",keyword:"maximum",params:{comparison:"<=",limit:360},message:"must be <= 360"}],!1;if(a<-360||isNaN(a))return v.errors=[{instancePath:n+"/rotation",schemaPath:"#/properties/rotation/minimum",keyword:"minimum",params:{comparison:">=",limit:-360},message:"must be >= -360"}],!1}else return v.errors=[{instancePath:n+"/rotation",schemaPath:"#/properties/rotation/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var p=l===r}else var p=!0;if(p){if(e.defaultYaw!==void 0){let a=e.defaultYaw,l=r;if(r===l)if(typeof a=="number"&&isFinite(a)){if(a>360||isNaN(a))return v.errors=[{instancePath:n+"/defaultYaw",schemaPath:"#/properties/defaultYaw/maximum",keyword:"maximum",params:{comparison:"<=",limit:360},message:"must be <= 360"}],!1;if(a<-360||isNaN(a))return v.errors=[{instancePath:n+"/defaultYaw",schemaPath:"#/properties/defaultYaw/minimum",keyword:"minimum",params:{comparison:">=",limit:-360},message:"must be >= -360"}],!1}else return v.errors=[{instancePath:n+"/defaultYaw",schemaPath:"#/properties/defaultYaw/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var p=l===r}else var p=!0;if(p){if(e.defaultTilt!==void 0){let a=e.defaultTilt,l=r;if(r===l)if(typeof a=="number"&&isFinite(a)){if(a>30||isNaN(a))return v.errors=[{instancePath:n+"/defaultTilt",schemaPath:"#/properties/defaultTilt/maximum",keyword:"maximum",params:{comparison:"<=",limit:30},message:"must be <= 30"}],!1;if(a<-90||isNaN(a))return v.errors=[{instancePath:n+"/defaultTilt",schemaPath:"#/properties/defaultTilt/minimum",keyword:"minimum",params:{comparison:">=",limit:-90},message:"must be >= -90"}],!1}else return v.errors=[{instancePath:n+"/defaultTilt",schemaPath:"#/properties/defaultTilt/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var p=l===r}else var p=!0;if(p)if(e.defaultViewFov!==void 0){let a=e.defaultViewFov,l=r;if(r===l)if(typeof a=="number"&&isFinite(a)){if(a>120||isNaN(a))return v.errors=[{instancePath:n+"/defaultViewFov",schemaPath:"#/properties/defaultViewFov/maximum",keyword:"maximum",params:{comparison:"<=",limit:120},message:"must be <= 120"}],!1;if(a<20||isNaN(a))return v.errors=[{instancePath:n+"/defaultViewFov",schemaPath:"#/properties/defaultViewFov/minimum",keyword:"minimum",params:{comparison:">=",limit:20},message:"must be >= 20"}],!1}else return v.errors=[{instancePath:n+"/defaultViewFov",schemaPath:"#/properties/defaultViewFov/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var p=l===r}else var p=!0}}}}}}}}}}else return v.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return v.errors=t,r===0}v.evaluated={props:{version:!0,projection:!0,mount:!0,centerX:!0,centerY:!0,radius:!0,fov:!0,rotation:!0,defaultYaw:!0,defaultTilt:!0,defaultViewFov:!0},dynamicProps:!1,dynamicItems:!1};function F(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:_=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=F.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(typeof e=="string"){if(x(e)>1023)return F.errors=[{instancePath:n,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:1023},message:"must NOT have more than 1023 characters"}],!1;if(new TextEncoder().encode(e).byteLength>1023)return F.errors=[{instancePath:n,schemaPath:"#/x-lightnvr-max-bytes",keyword:"x-lightnvr-max-bytes",params:{},message:"must fit the UTF-8 byte limit"}],!1;if(e!==""){let y,a=!0;try{y=JSON.parse(e)}catch{a=!1}if(a){let l=r;v(y,{instancePath:n,parentData:e,parentDataProperty:g,rootData:_,dynamicAnchors:d})||(t=t===null?v.errors:t.concat(v.errors),r=t.length);var p=l===r;a=p}if(!a)return F.errors=[{instancePath:n,schemaPath:"#/contentSchema",keyword:"contentSchema",params:{},message:"must contain valid JSON matching contentSchema (or be empty)"}],!1}}else return F.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return F.errors=t,r===0}F.evaluated={dynamicProps:!1,dynamicItems:!1};function h(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:_=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=h.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),!(typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)))return h.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;if(r===0&&typeof e=="number"&&isFinite(e)){if(e>2147483647||isNaN(e))return h.errors=[{instancePath:n,schemaPath:"#/maximum",keyword:"maximum",params:{comparison:"<=",limit:2147483647},message:"must be <= 2147483647"}],!1;if(e<-2147483648||isNaN(e))return h.errors=[{instancePath:n,schemaPath:"#/minimum",keyword:"minimum",params:{comparison:">=",limit:-2147483648},message:"must be >= -2147483648"}],!1}return h.errors=t,r===0}h.evaluated={dynamicProps:!1,dynamicItems:!1};var M={type:"integer",enum:[0,1],$id:"urn:lightnvr:contract:StreamProtocol"};function E(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:_=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=E.evaluated;return o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)?e===0||e===1?(E.errors=t,r===0):(E.errors=[{instancePath:n,schemaPath:"#/enum",keyword:"enum",params:{allowedValues:M.enum},message:"must be equal to one of the allowed values"}],!1):(E.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1)}E.evaluated={dynamicProps:!1,dynamicItems:!1};function O(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:_=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=O.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),!(typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)))return O.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;if(r===0&&typeof e=="number"&&isFinite(e)){if(e>365||isNaN(e))return O.errors=[{instancePath:n,schemaPath:"#/maximum",keyword:"maximum",params:{comparison:"<=",limit:365},message:"must be <= 365"}],!1;if(e<-1||isNaN(e))return O.errors=[{instancePath:n,schemaPath:"#/minimum",keyword:"minimum",params:{comparison:">=",limit:-1},message:"must be >= -1"}],!1}return O.errors=t,r===0}O.evaluated={dynamicProps:!1,dynamicItems:!1};function D(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:_=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=D.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(Array.isArray(e)){if(e.length>168)return D.errors=[{instancePath:n,schemaPath:"#/maxItems",keyword:"maxItems",params:{limit:168},message:"must NOT have more than 168 items"}],!1;if(e.length<168)return D.errors=[{instancePath:n,schemaPath:"#/minItems",keyword:"minItems",params:{limit:168},message:"must NOT have fewer than 168 items"}],!1;{var p=!0;let y=e.length;for(let a=0;a511)return f.errors=[{instancePath:n+"/url",schemaPath:"#/properties/url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return f.errors=[{instancePath:n+"/url",schemaPath:"#/properties/url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=c===r}else var i=!0;if(i){if(e.enabled!==void 0){let s=r;if(typeof e.enabled!="boolean")return f.errors=[{instancePath:n+"/enabled",schemaPath:"#/properties/enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.streaming_enabled!==void 0){let s=r;if(typeof e.streaming_enabled!="boolean")return f.errors=[{instancePath:n+"/streaming_enabled",schemaPath:"#/properties/streaming_enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.playback_transport!==void 0){let s=r;T(e.playback_transport,{instancePath:n+"/playback_transport",parentData:e,parentDataProperty:"playback_transport",rootData:_,dynamicAnchors:d})||(t=t===null?T.errors:t.concat(T.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.eptz_config!==void 0){let s=r;F(e.eptz_config,{instancePath:n+"/eptz_config",parentData:e,parentDataProperty:"eptz_config",rootData:_,dynamicAnchors:d})||(t=t===null?F.errors:t.concat(F.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.width!==void 0){let s=r;h(e.width,{instancePath:n+"/width",parentData:e,parentDataProperty:"width",rootData:_,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.height!==void 0){let s=r;h(e.height,{instancePath:n+"/height",parentData:e,parentDataProperty:"height",rootData:_,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.fps!==void 0){let s=r;h(e.fps,{instancePath:n+"/fps",parentData:e,parentDataProperty:"fps",rootData:_,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.codec!==void 0){let s=e.codec,c=r;if(r===c)if(typeof s=="string"){if(x(s)>15)return f.errors=[{instancePath:n+"/codec",schemaPath:"#/properties/codec/maxLength",keyword:"maxLength",params:{limit:15},message:"must NOT have more than 15 characters"}],!1}else return f.errors=[{instancePath:n+"/codec",schemaPath:"#/properties/codec/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=c===r}else var i=!0;if(i){if(e.priority!==void 0){let s=r;h(e.priority,{instancePath:n+"/priority",parentData:e,parentDataProperty:"priority",rootData:_,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.record!==void 0){let s=r;if(typeof e.record!="boolean")return f.errors=[{instancePath:n+"/record",schemaPath:"#/properties/record/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.segment_duration!==void 0){let s=r;h(e.segment_duration,{instancePath:n+"/segment_duration",parentData:e,parentDataProperty:"segment_duration",rootData:_,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.detection_based_recording!==void 0){let s=r;if(typeof e.detection_based_recording!="boolean")return f.errors=[{instancePath:n+"/detection_based_recording",schemaPath:"#/properties/detection_based_recording/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.detection_model!==void 0){let s=e.detection_model,c=r;if(r===c)if(typeof s=="string"){if(x(s)>511)return f.errors=[{instancePath:n+"/detection_model",schemaPath:"#/properties/detection_model/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return f.errors=[{instancePath:n+"/detection_model",schemaPath:"#/properties/detection_model/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=c===r}else var i=!0;if(i){if(e.detection_threshold!==void 0){let s=e.detection_threshold,c=r;if(!(typeof s=="number"&&!(s%1)&&!isNaN(s)&&isFinite(s)))return f.errors=[{instancePath:n+"/detection_threshold",schemaPath:"#/properties/detection_threshold/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;var i=c===r}else var i=!0;if(i){if(e.detection_interval!==void 0){let s=r;h(e.detection_interval,{instancePath:n+"/detection_interval",parentData:e,parentDataProperty:"detection_interval",rootData:_,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.pre_detection_buffer!==void 0){let s=r;h(e.pre_detection_buffer,{instancePath:n+"/pre_detection_buffer",parentData:e,parentDataProperty:"pre_detection_buffer",rootData:_,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.post_detection_buffer!==void 0){let s=r;h(e.post_detection_buffer,{instancePath:n+"/post_detection_buffer",parentData:e,parentDataProperty:"post_detection_buffer",rootData:_,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.detection_object_filter!==void 0){let s=e.detection_object_filter,c=r;if(r===c)if(typeof s=="string"){if(x(s)>15)return f.errors=[{instancePath:n+"/detection_object_filter",schemaPath:"#/properties/detection_object_filter/maxLength",keyword:"maxLength",params:{limit:15},message:"must NOT have more than 15 characters"}],!1}else return f.errors=[{instancePath:n+"/detection_object_filter",schemaPath:"#/properties/detection_object_filter/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=c===r}else var i=!0;if(i){if(e.detection_object_filter_list!==void 0){let s=e.detection_object_filter_list,c=r;if(r===c)if(typeof s=="string"){if(x(s)>255)return f.errors=[{instancePath:n+"/detection_object_filter_list",schemaPath:"#/properties/detection_object_filter_list/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1}else return f.errors=[{instancePath:n+"/detection_object_filter_list",schemaPath:"#/properties/detection_object_filter_list/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=c===r}else var i=!0;if(i){if(e.protocol!==void 0){let s=r;E(e.protocol,{instancePath:n+"/protocol",parentData:e,parentDataProperty:"protocol",rootData:_,dynamicAnchors:d})||(t=t===null?E.errors:t.concat(E.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.record_audio!==void 0){let s=r;if(typeof e.record_audio!="boolean")return f.errors=[{instancePath:n+"/record_audio",schemaPath:"#/properties/record_audio/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.audio_voice_enhancement!==void 0){let s=r;if(typeof e.audio_voice_enhancement!="boolean")return f.errors=[{instancePath:n+"/audio_voice_enhancement",schemaPath:"#/properties/audio_voice_enhancement/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.isOnvif!==void 0){let s=r;if(typeof e.isOnvif!="boolean")return f.errors=[{instancePath:n+"/isOnvif",schemaPath:"#/properties/isOnvif/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.backchannel_enabled!==void 0){let s=r;if(typeof e.backchannel_enabled!="boolean")return f.errors=[{instancePath:n+"/backchannel_enabled",schemaPath:"#/properties/backchannel_enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.retention_days!==void 0){let s=r;O(e.retention_days,{instancePath:n+"/retention_days",parentData:e,parentDataProperty:"retention_days",rootData:_,dynamicAnchors:d})||(t=t===null?O.errors:t.concat(O.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.detection_retention_days!==void 0){let s=r;O(e.detection_retention_days,{instancePath:n+"/detection_retention_days",parentData:e,parentDataProperty:"detection_retention_days",rootData:_,dynamicAnchors:d})||(t=t===null?O.errors:t.concat(O.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.max_storage_mb!==void 0){let s=r;h(e.max_storage_mb,{instancePath:n+"/max_storage_mb",parentData:e,parentDataProperty:"max_storage_mb",rootData:_,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.tier_critical_multiplier!==void 0){let s=e.tier_critical_multiplier,c=r;if(!(typeof s=="number"&&isFinite(s)))return f.errors=[{instancePath:n+"/tier_critical_multiplier",schemaPath:"#/properties/tier_critical_multiplier/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var i=c===r}else var i=!0;if(i){if(e.tier_important_multiplier!==void 0){let s=e.tier_important_multiplier,c=r;if(!(typeof s=="number"&&isFinite(s)))return f.errors=[{instancePath:n+"/tier_important_multiplier",schemaPath:"#/properties/tier_important_multiplier/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var i=c===r}else var i=!0;if(i){if(e.tier_ephemeral_multiplier!==void 0){let s=e.tier_ephemeral_multiplier,c=r;if(!(typeof s=="number"&&isFinite(s)))return f.errors=[{instancePath:n+"/tier_ephemeral_multiplier",schemaPath:"#/properties/tier_ephemeral_multiplier/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var i=c===r}else var i=!0;if(i){if(e.storage_priority!==void 0){let s=r;h(e.storage_priority,{instancePath:n+"/storage_priority",parentData:e,parentDataProperty:"storage_priority",rootData:_,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.ptz_enabled!==void 0){let s=r;if(typeof e.ptz_enabled!="boolean")return f.errors=[{instancePath:n+"/ptz_enabled",schemaPath:"#/properties/ptz_enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.ptz_max_x!==void 0){let s=r;h(e.ptz_max_x,{instancePath:n+"/ptz_max_x",parentData:e,parentDataProperty:"ptz_max_x",rootData:_,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.ptz_max_y!==void 0){let s=r;h(e.ptz_max_y,{instancePath:n+"/ptz_max_y",parentData:e,parentDataProperty:"ptz_max_y",rootData:_,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.ptz_max_z!==void 0){let s=r;h(e.ptz_max_z,{instancePath:n+"/ptz_max_z",parentData:e,parentDataProperty:"ptz_max_z",rootData:_,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.ptz_has_home!==void 0){let s=r;if(typeof e.ptz_has_home!="boolean")return f.errors=[{instancePath:n+"/ptz_has_home",schemaPath:"#/properties/ptz_has_home/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.onvif_username!==void 0){let s=e.onvif_username,c=r;if(r===c)if(typeof s=="string"){if(x(s)>63)return f.errors=[{instancePath:n+"/onvif_username",schemaPath:"#/properties/onvif_username/maxLength",keyword:"maxLength",params:{limit:63},message:"must NOT have more than 63 characters"}],!1}else return f.errors=[{instancePath:n+"/onvif_username",schemaPath:"#/properties/onvif_username/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=c===r}else var i=!0;if(i){if(e.onvif_password!==void 0){let s=e.onvif_password,c=r;if(r===c)if(typeof s=="string"){if(x(s)>63)return f.errors=[{instancePath:n+"/onvif_password",schemaPath:"#/properties/onvif_password/maxLength",keyword:"maxLength",params:{limit:63},message:"must NOT have more than 63 characters"}],!1}else return f.errors=[{instancePath:n+"/onvif_password",schemaPath:"#/properties/onvif_password/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=c===r}else var i=!0;if(i){if(e.onvif_profile!==void 0){let s=e.onvif_profile,c=r;if(r===c)if(typeof s=="string"){if(x(s)>63)return f.errors=[{instancePath:n+"/onvif_profile",schemaPath:"#/properties/onvif_profile/maxLength",keyword:"maxLength",params:{limit:63},message:"must NOT have more than 63 characters"}],!1}else return f.errors=[{instancePath:n+"/onvif_profile",schemaPath:"#/properties/onvif_profile/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=c===r}else var i=!0;if(i){if(e.onvif_port!==void 0){let s=r;h(e.onvif_port,{instancePath:n+"/onvif_port",parentData:e,parentDataProperty:"onvif_port",rootData:_,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.record_on_schedule!==void 0){let s=r;if(typeof e.record_on_schedule!="boolean")return f.errors=[{instancePath:n+"/record_on_schedule",schemaPath:"#/properties/record_on_schedule/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.recording_schedule!==void 0){let s=r;D(e.recording_schedule,{instancePath:n+"/recording_schedule",parentData:e,parentDataProperty:"recording_schedule",rootData:_,dynamicAnchors:d})||(t=t===null?D.errors:t.concat(D.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.detection_record_on_schedule!==void 0){let s=r;if(typeof e.detection_record_on_schedule!="boolean")return f.errors=[{instancePath:n+"/detection_record_on_schedule",schemaPath:"#/properties/detection_record_on_schedule/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.detection_recording_schedule!==void 0){let s=r;D(e.detection_recording_schedule,{instancePath:n+"/detection_recording_schedule",parentData:e,parentDataProperty:"detection_recording_schedule",rootData:_,dynamicAnchors:d})||(t=t===null?D.errors:t.concat(D.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.tags!==void 0){let s=e.tags,c=r;if(r===c)if(typeof s=="string"){if(x(s)>255)return f.errors=[{instancePath:n+"/tags",schemaPath:"#/properties/tags/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1}else return f.errors=[{instancePath:n+"/tags",schemaPath:"#/properties/tags/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=c===r}else var i=!0;if(i){if(e.admin_url!==void 0){let s=e.admin_url,c=r;if(r===c)if(typeof s=="string"){if(x(s)>511)return f.errors=[{instancePath:n+"/admin_url",schemaPath:"#/properties/admin_url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return f.errors=[{instancePath:n+"/admin_url",schemaPath:"#/properties/admin_url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=c===r}else var i=!0;if(i){if(e.privacy_mode!==void 0){let s=r;if(typeof e.privacy_mode!="boolean")return f.errors=[{instancePath:n+"/privacy_mode",schemaPath:"#/properties/privacy_mode/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.can_control_privacy!==void 0){let s=r;if(typeof e.can_control_privacy!="boolean")return f.errors=[{instancePath:n+"/can_control_privacy",schemaPath:"#/properties/can_control_privacy/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.motion_trigger_source!==void 0){let s=e.motion_trigger_source,c=r;if(r===c)if(typeof s=="string"){if(x(s)>255)return f.errors=[{instancePath:n+"/motion_trigger_source",schemaPath:"#/properties/motion_trigger_source/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1}else return f.errors=[{instancePath:n+"/motion_trigger_source",schemaPath:"#/properties/motion_trigger_source/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=c===r}else var i=!0;if(i){if(e.go2rtc_source_override!==void 0){let s=e.go2rtc_source_override,c=r;if(r===c)if(typeof s=="string"){if(x(s)>8191)return f.errors=[{instancePath:n+"/go2rtc_source_override",schemaPath:"#/properties/go2rtc_source_override/maxLength",keyword:"maxLength",params:{limit:8191},message:"must NOT have more than 8191 characters"}],!1}else return f.errors=[{instancePath:n+"/go2rtc_source_override",schemaPath:"#/properties/go2rtc_source_override/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=c===r}else var i=!0;if(i){if(e.sub_stream_url!==void 0){let s=e.sub_stream_url,c=r;if(r===c)if(typeof s=="string"){if(x(s)>511)return f.errors=[{instancePath:n+"/sub_stream_url",schemaPath:"#/properties/sub_stream_url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return f.errors=[{instancePath:n+"/sub_stream_url",schemaPath:"#/properties/sub_stream_url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=c===r}else var i=!0;if(i){if(e.has_sub_stream!==void 0){let s=r;if(typeof e.has_sub_stream!="boolean")return f.errors=[{instancePath:n+"/has_sub_stream",schemaPath:"#/properties/has_sub_stream/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.detection_url!==void 0){let s=e.detection_url,c=r;if(r===c)if(typeof s=="string"){if(x(s)>511)return f.errors=[{instancePath:n+"/detection_url",schemaPath:"#/properties/detection_url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return f.errors=[{instancePath:n+"/detection_url",schemaPath:"#/properties/detection_url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=c===r}else var i=!0;if(i){if(e.publish_url!==void 0){let s=e.publish_url,c=r;if(r===c)if(typeof s=="string"){if(x(s)>511)return f.errors=[{instancePath:n+"/publish_url",schemaPath:"#/properties/publish_url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return f.errors=[{instancePath:n+"/publish_url",schemaPath:"#/properties/publish_url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=c===r}else var i=!0;if(i){if(e.status!==void 0){let s=r;A(e.status,{instancePath:n+"/status",parentData:e,parentDataProperty:"status",rootData:_,dynamicAnchors:d})||(t=t===null?A.errors:t.concat(A.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.error_message!==void 0){let s=e.error_message,c=r;if(r===c)if(typeof s=="string"){if(x(s)>255)return f.errors=[{instancePath:n+"/error_message",schemaPath:"#/properties/error_message/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1;if(x(s)<1)return f.errors=[{instancePath:n+"/error_message",schemaPath:"#/properties/error_message/minLength",keyword:"minLength",params:{limit:1},message:"must NOT have fewer than 1 characters"}],!1}else return f.errors=[{instancePath:n+"/error_message",schemaPath:"#/properties/error_message/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=c===r}else var i=!0;if(i)if(e.go2rtc_hls_available!==void 0){let s=r;if(typeof e.go2rtc_hls_available!="boolean")return f.errors=[{instancePath:n+"/go2rtc_hls_available",schemaPath:"#/properties/go2rtc_hls_available/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}else return f.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return f.errors=t,r===0}f.evaluated={props:{camera_uuid:!0,location_uuid:!0,name:!0,url:!0,enabled:!0,streaming_enabled:!0,playback_transport:!0,eptz_config:!0,width:!0,height:!0,fps:!0,codec:!0,priority:!0,record:!0,segment_duration:!0,detection_based_recording:!0,detection_model:!0,detection_threshold:!0,detection_interval:!0,pre_detection_buffer:!0,post_detection_buffer:!0,detection_object_filter:!0,detection_object_filter_list:!0,protocol:!0,record_audio:!0,audio_voice_enhancement:!0,isOnvif:!0,backchannel_enabled:!0,retention_days:!0,detection_retention_days:!0,max_storage_mb:!0,tier_critical_multiplier:!0,tier_important_multiplier:!0,tier_ephemeral_multiplier:!0,storage_priority:!0,ptz_enabled:!0,ptz_max_x:!0,ptz_max_y:!0,ptz_max_z:!0,ptz_has_home:!0,onvif_username:!0,onvif_password:!0,onvif_profile:!0,onvif_port:!0,record_on_schedule:!0,recording_schedule:!0,detection_record_on_schedule:!0,detection_recording_schedule:!0,tags:!0,admin_url:!0,privacy_mode:!0,can_control_privacy:!0,motion_trigger_source:!0,go2rtc_source_override:!0,sub_stream_url:!0,has_sub_stream:!0,detection_url:!0,publish_url:!0,status:!0,error_message:!0,go2rtc_hls_available:!0},dynamicProps:!1,dynamicItems:!1};var G=new RegExp("^(?![A-Za-z][A-Za-z0-9+.-]*://[^/?#]*@)","u");function L(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:_=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=L.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(typeof e=="string"){if(x(e)>511)return L.errors=[{instancePath:n,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1;if(!G.test(e))return L.errors=[{instancePath:n,schemaPath:"#/pattern",keyword:"pattern",params:{pattern:"^(?![A-Za-z][A-Za-z0-9+.-]*://[^/?#]*@)"},message:'must match pattern "^(?![A-Za-z][A-Za-z0-9+.-]*://[^/?#]*@)"'}],!1}else return L.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return L.errors=t,r===0}L.evaluated={dynamicProps:!1,dynamicItems:!1};function j(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:_=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=j.evaluated;o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0);let p=r;f(e,{instancePath:n,parentData:b,parentDataProperty:g,rootData:_,dynamicAnchors:d})||(t=t===null?f.errors:t.concat(f.errors),r=t.length);var y=p===r;if(y){let $=r,i=r,m=!0,s=r,c=r,z=!1,X=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let P;if(e.isOnvif===void 0&&(P="isOnvif")){let k={};t===null?t=[k]:t.push(k),r++}else if(e.isOnvif!==void 0&&e.isOnvif!==!0){let k={};t===null?t=[k]:t.push(k),r++}}var l=X===r;if(z=z||l,l){var a={};a.isOnvif=!0}let C=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let P;if(e.onvif_username===void 0&&(P="onvif_username")){let k={};t===null?t=[k]:t.push(k),r++}else if(e.onvif_username!==void 0){let k=e.onvif_username;if(typeof k=="string"&&x(k)<1){let V={};t===null?t=[V]:t.push(V),r++}}}var l=C===r;z=z||l,l&&a!==!0&&(a=a||{},a.onvif_username=!0);let K=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let P;if(e.onvif_password===void 0&&(P="onvif_password")){let k={};t===null?t=[k]:t.push(k),r++}else if(e.onvif_password!==void 0){let k=e.onvif_password;if(typeof k=="string"&&x(k)<1){let V={};t===null?t=[V]:t.push(V),r++}}}var l=K===r;if(z=z||l,l&&a!==!0&&(a=a||{},a.onvif_password=!0),z)r=c,t!==null&&(c?t.length=c:t=null);else{let P={};t===null?t=[P]:t.push(P),r++}var u=s===r;if(r=i,t!==null&&(i?t.length=i:t=null),u){let P=r;e&&typeof e=="object"&&!Array.isArray(e)&&e.url!==void 0&&(L(e.url,{instancePath:n+"/url",parentData:e,parentDataProperty:"url",rootData:_,dynamicAnchors:d})||(t=t===null?L.errors:t.concat(L.errors),r=t.length));var u=P===r;m=u,m&&a!==!0&&(a=a||{},a.url=!0)}if(!m){let P={instancePath:n,schemaPath:"#/allOf/1/if",keyword:"if",params:{failingKeyword:"then"},message:'must match "then" schema'};return t===null?t=[P]:t.push(P),r++,j.errors=t,!1}if(r===$)if(e&&typeof e=="object"&&!Array.isArray(e)){let P;if(e.url===void 0&&(P="url")||e.onvif_username===void 0&&(P="onvif_username")||e.onvif_password===void 0&&(P="onvif_password")||e.admin_url===void 0&&(P="admin_url")||e.go2rtc_source_override===void 0&&(P="go2rtc_source_override")||e.sub_stream_url===void 0&&(P="sub_stream_url")||e.detection_url===void 0&&(P="detection_url")||e.publish_url===void 0&&(P="publish_url"))return j.errors=[{instancePath:n,schemaPath:"#/allOf/1/required",keyword:"required",params:{missingProperty:P},message:"must have required property '"+P+"'"}],!1;if(a!==!0&&(a=a||{},a.can_control_privacy=!0),e.can_control_privacy!==void 0&&e.can_control_privacy!==!0)return j.errors=[{instancePath:n+"/can_control_privacy",schemaPath:"#/allOf/1/properties/can_control_privacy/const",keyword:"const",params:{allowedValue:!0},message:"must be equal to constant"}],!1}else return j.errors=[{instancePath:n,schemaPath:"#/allOf/1/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;var y=$===r;y&&a!==!0&&(a=a||{},a.camera_uuid=!0,a.location_uuid=!0,a.name=!0,a.url=!0,a.enabled=!0,a.streaming_enabled=!0,a.playback_transport=!0,a.eptz_config=!0,a.width=!0,a.height=!0,a.fps=!0,a.codec=!0,a.priority=!0,a.record=!0,a.segment_duration=!0,a.detection_based_recording=!0,a.detection_model=!0,a.detection_threshold=!0,a.detection_interval=!0,a.pre_detection_buffer=!0,a.post_detection_buffer=!0,a.detection_object_filter=!0,a.detection_object_filter_list=!0,a.protocol=!0,a.record_audio=!0,a.audio_voice_enhancement=!0,a.isOnvif=!0,a.backchannel_enabled=!0,a.retention_days=!0,a.detection_retention_days=!0,a.max_storage_mb=!0,a.tier_critical_multiplier=!0,a.tier_important_multiplier=!0,a.tier_ephemeral_multiplier=!0,a.storage_priority=!0,a.ptz_enabled=!0,a.ptz_max_x=!0,a.ptz_max_y=!0,a.ptz_max_z=!0,a.ptz_has_home=!0,a.onvif_username=!0,a.onvif_password=!0,a.onvif_profile=!0,a.onvif_port=!0,a.record_on_schedule=!0,a.recording_schedule=!0,a.detection_record_on_schedule=!0,a.detection_recording_schedule=!0,a.tags=!0,a.admin_url=!0,a.privacy_mode=!0,a.can_control_privacy=!0,a.motion_trigger_source=!0,a.go2rtc_source_override=!0,a.sub_stream_url=!0,a.has_sub_stream=!0,a.detection_url=!0,a.publish_url=!0,a.status=!0,a.error_message=!0,a.go2rtc_hls_available=!0)}return j.errors=t,o.props=a,r===0}j.evaluated={dynamicProps:!0,dynamicItems:!1};var ee=new RegExp("[?&;][^=&#]*([Pp][Aa][Ss][Ss]|[Pp][Ww][Dd]|[Tt][Oo][Kk][Ee][Nn]|[Ss][Ee][Cc][Rr][Ee][Tt]|[Aa][Pp][Ii]_?[Kk][Ee][Yy])[^=&#]*=[^&#]","u");function q(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:_=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=q.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e!="string")return q.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;let p=r,y=r;if(typeof e=="string"&&!ee.test(e)){let l={};t===null?t=[l]:t.push(l),r++}var a=y===r;return a?(q.errors=[{instancePath:n,schemaPath:"#/not",keyword:"not",params:{},message:"must NOT be valid"}],!1):(r=p,t!==null&&(p?t.length=p:t=null),q.errors=t,r===0)}q.evaluated={dynamicProps:!1,dynamicItems:!1};function Y(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:_=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=Y.evaluated;o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0);let p=r;L(e,{instancePath:n,parentData:b,parentDataProperty:g,rootData:_,dynamicAnchors:d})||(t=t===null?L.errors:t.concat(L.errors),r=t.length);var y=p===r;if(y){let a=r;q(e,{instancePath:n,parentData:b,parentDataProperty:g,rootData:_,dynamicAnchors:d})||(t=t===null?q.errors:t.concat(q.errors),r=t.length);var y=a===r}return Y.errors=t,r===0}Y.evaluated={dynamicProps:!1,dynamicItems:!1};function w(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:_=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=w.evaluated;return o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e!="string"?(w.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1):e!==""?(w.errors=[{instancePath:n,schemaPath:"#/const",keyword:"const",params:{allowedValue:""},message:"must be equal to constant"}],!1):(w.errors=t,r===0)}w.evaluated={dynamicProps:!1,dynamicItems:!1};function S(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:_=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=S.evaluated;o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0);let p=r;f(e,{instancePath:n,parentData:b,parentDataProperty:g,rootData:_,dynamicAnchors:d})||(t=t===null?f.errors:t.concat(f.errors),r=t.length);var y=p===r;if(y){let u=r,$=r,i=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let s;if(e.detection_api_url===void 0&&(s="detection_api_url")){let c={};t===null?t=[c]:t.push(c),r++}}var a=i===r;if(a)return S.errors=[{instancePath:n,schemaPath:"#/allOf/1/not",keyword:"not",params:{},message:"must NOT be valid"}],!1;if(r=$,t!==null&&($?t.length=$:t=null),r===u)if(e&&typeof e=="object"&&!Array.isArray(e)){if(e.can_control_privacy!==void 0){let s=r;if(e.can_control_privacy!==!1)return S.errors=[{instancePath:n+"/can_control_privacy",schemaPath:"#/allOf/1/properties/can_control_privacy/const",keyword:"const",params:{allowedValue:!1},message:"must be equal to constant"}],!1;var l=s===r}else var l=!0;if(l){if(e.url!==void 0){let s=r;Y(e.url,{instancePath:n+"/url",parentData:e,parentDataProperty:"url",rootData:_,dynamicAnchors:d})||(t=t===null?Y.errors:t.concat(Y.errors),r=t.length);var l=s===r}else var l=!0;if(l){if(e.onvif_username!==void 0){let s=r;w(e.onvif_username,{instancePath:n+"/onvif_username",parentData:e,parentDataProperty:"onvif_username",rootData:_,dynamicAnchors:d})||(t=t===null?w.errors:t.concat(w.errors),r=t.length);var l=s===r}else var l=!0;if(l){if(e.onvif_password!==void 0){let s=r;w(e.onvif_password,{instancePath:n+"/onvif_password",parentData:e,parentDataProperty:"onvif_password",rootData:_,dynamicAnchors:d})||(t=t===null?w.errors:t.concat(w.errors),r=t.length);var l=s===r}else var l=!0;if(l){if(e.admin_url!==void 0){let s=r;w(e.admin_url,{instancePath:n+"/admin_url",parentData:e,parentDataProperty:"admin_url",rootData:_,dynamicAnchors:d})||(t=t===null?w.errors:t.concat(w.errors),r=t.length);var l=s===r}else var l=!0;if(l){if(e.go2rtc_source_override!==void 0){let s=r;w(e.go2rtc_source_override,{instancePath:n+"/go2rtc_source_override",parentData:e,parentDataProperty:"go2rtc_source_override",rootData:_,dynamicAnchors:d})||(t=t===null?w.errors:t.concat(w.errors),r=t.length);var l=s===r}else var l=!0;if(l){if(e.sub_stream_url!==void 0){let s=r;w(e.sub_stream_url,{instancePath:n+"/sub_stream_url",parentData:e,parentDataProperty:"sub_stream_url",rootData:_,dynamicAnchors:d})||(t=t===null?w.errors:t.concat(w.errors),r=t.length);var l=s===r}else var l=!0;if(l){if(e.detection_url!==void 0){let s=r;w(e.detection_url,{instancePath:n+"/detection_url",parentData:e,parentDataProperty:"detection_url",rootData:_,dynamicAnchors:d})||(t=t===null?w.errors:t.concat(w.errors),r=t.length);var l=s===r}else var l=!0;if(l)if(e.publish_url!==void 0){let s=r;w(e.publish_url,{instancePath:n+"/publish_url",parentData:e,parentDataProperty:"publish_url",rootData:_,dynamicAnchors:d})||(t=t===null?w.errors:t.concat(w.errors),r=t.length);var l=s===r}else var l=!0}}}}}}}}else return S.errors=[{instancePath:n,schemaPath:"#/allOf/1/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;var y=u===r}return S.errors=t,r===0}S.evaluated={props:{can_control_privacy:!0,url:!0,onvif_username:!0,onvif_password:!0,admin_url:!0,go2rtc_source_override:!0,sub_stream_url:!0,detection_url:!0,publish_url:!0,camera_uuid:!0,location_uuid:!0,name:!0,enabled:!0,streaming_enabled:!0,playback_transport:!0,eptz_config:!0,width:!0,height:!0,fps:!0,codec:!0,priority:!0,record:!0,segment_duration:!0,detection_based_recording:!0,detection_model:!0,detection_threshold:!0,detection_interval:!0,pre_detection_buffer:!0,post_detection_buffer:!0,detection_object_filter:!0,detection_object_filter_list:!0,protocol:!0,record_audio:!0,audio_voice_enhancement:!0,isOnvif:!0,backchannel_enabled:!0,retention_days:!0,detection_retention_days:!0,max_storage_mb:!0,tier_critical_multiplier:!0,tier_important_multiplier:!0,tier_ephemeral_multiplier:!0,storage_priority:!0,ptz_enabled:!0,ptz_max_x:!0,ptz_max_y:!0,ptz_max_z:!0,ptz_has_home:!0,onvif_profile:!0,onvif_port:!0,record_on_schedule:!0,recording_schedule:!0,detection_record_on_schedule:!0,detection_recording_schedule:!0,tags:!0,privacy_mode:!0,motion_trigger_source:!0,has_sub_stream:!0,status:!0,error_message:!0,go2rtc_hls_available:!0},dynamicProps:!1,dynamicItems:!1};function U(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:_=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=U.evaluated;o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0);let p=r,y=!1,a=null,l=r;if(!j(e,{instancePath:n,parentData:b,parentDataProperty:g,rootData:_,dynamicAnchors:d}))t=t===null?j.errors:t.concat(j.errors),r=t.length;else var u=j.evaluated.props;var i=l===r;i&&(y=!0,a=0);let $=r;S(e,{instancePath:n,parentData:b,parentDataProperty:g,rootData:_,dynamicAnchors:d})||(t=t===null?S.errors:t.concat(S.errors),r=t.length);var i=$===r;if(i&&y?(y=!1,a=[a,1]):i&&(y=!0,a=1,u!==!0&&(u=u||{},u.can_control_privacy=!0,u.url=!0,u.onvif_username=!0,u.onvif_password=!0,u.admin_url=!0,u.go2rtc_source_override=!0,u.sub_stream_url=!0,u.detection_url=!0,u.publish_url=!0,u.camera_uuid=!0,u.location_uuid=!0,u.name=!0,u.enabled=!0,u.streaming_enabled=!0,u.playback_transport=!0,u.eptz_config=!0,u.width=!0,u.height=!0,u.fps=!0,u.codec=!0,u.priority=!0,u.record=!0,u.segment_duration=!0,u.detection_based_recording=!0,u.detection_model=!0,u.detection_threshold=!0,u.detection_interval=!0,u.pre_detection_buffer=!0,u.post_detection_buffer=!0,u.detection_object_filter=!0,u.detection_object_filter_list=!0,u.protocol=!0,u.record_audio=!0,u.audio_voice_enhancement=!0,u.isOnvif=!0,u.backchannel_enabled=!0,u.retention_days=!0,u.detection_retention_days=!0,u.max_storage_mb=!0,u.tier_critical_multiplier=!0,u.tier_important_multiplier=!0,u.tier_ephemeral_multiplier=!0,u.storage_priority=!0,u.ptz_enabled=!0,u.ptz_max_x=!0,u.ptz_max_y=!0,u.ptz_max_z=!0,u.ptz_has_home=!0,u.onvif_profile=!0,u.onvif_port=!0,u.record_on_schedule=!0,u.recording_schedule=!0,u.detection_record_on_schedule=!0,u.detection_recording_schedule=!0,u.tags=!0,u.privacy_mode=!0,u.motion_trigger_source=!0,u.has_sub_stream=!0,u.status=!0,u.error_message=!0,u.go2rtc_hls_available=!0)),y)r=p,t!==null&&(p?t.length=p:t=null);else{let m={instancePath:n,schemaPath:"#/oneOf",keyword:"oneOf",params:{passingSchemas:a},message:"must match exactly one schema in oneOf"};return t===null?t=[m]:t.push(m),r++,U.errors=t,!1}return U.errors=t,o.props=u,r===0}U.evaluated={dynamicProps:!0,dynamicItems:!1};export{ne as validateStreamDetail}; diff --git a/web/js/api/generated/validators/StreamDetailBase.d.mts b/web/js/api/generated/validators/StreamDetailBase.d.mts new file mode 100644 index 000000000..aaf3c8152 --- /dev/null +++ b/web/js/api/generated/validators/StreamDetailBase.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateStreamDetailBase } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/StreamDetailBase.mjs b/web/js/api/generated/validators/StreamDetailBase.mjs new file mode 100644 index 000000000..2cc96319a --- /dev/null +++ b/web/js/api/generated/validators/StreamDetailBase.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var E=(e,s)=>()=>{try{return s||e((s={exports:{}}).exports,s),s.exports}catch(b){throw s=0,b}};var $=E(T=>{"use strict";Object.defineProperty(T,"__esModule",{value:!0});function O(e){let s=e.length,b=0,v=0,f;for(;v=55296&&f<=56319&&v36)return x.errors=[{instancePath:s,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:36},message:"must NOT have more than 36 characters"}],!1;if(!q.test(e))return x.errors=[{instancePath:s,schemaPath:"#/pattern",keyword:"pattern",params:{pattern:"^(|[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})$"},message:'must match pattern "^(|[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})$"'}],!1}else return x.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return x.errors=n,r===0}x.evaluated={dynamicProps:!1,dynamicItems:!1};function k(e,{instancePath:s="",parentData:b,parentDataProperty:v,rootData:f=e,dynamicAnchors:d={}}={}){let n=null,r=0,u=k.evaluated;if(u.dynamicProps&&(u.props=void 0),u.dynamicItems&&(u.items=void 0),r===0)if(typeof e=="string"){if(g(e)>255)return k.errors=[{instancePath:s,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1;if(g(e)<1)return k.errors=[{instancePath:s,schemaPath:"#/minLength",keyword:"minLength",params:{limit:1},message:"must NOT have fewer than 1 characters"}],!1;if(new TextEncoder().encode(e).byteLength>255)return k.errors=[{instancePath:s,schemaPath:"#/x-lightnvr-max-bytes",keyword:"x-lightnvr-max-bytes",params:{},message:"must fit the UTF-8 byte limit"}],!1}else return k.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return k.errors=n,r===0}k.evaluated={dynamicProps:!1,dynamicItems:!1};var V={type:"string",enum:["auto","webrtc_only","mse_only","hls_only","webrtc_then_mse","mse_then_hls"],$id:"urn:lightnvr:contract:PlaybackTransport"};function z(e,{instancePath:s="",parentData:b,parentDataProperty:v,rootData:f=e,dynamicAnchors:d={}}={}){let n=null,r=0,u=z.evaluated;return u.dynamicProps&&(u.props=void 0),u.dynamicItems&&(u.items=void 0),typeof e!="string"?(z.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1):e==="auto"||e==="webrtc_only"||e==="mse_only"||e==="hls_only"||e==="webrtc_then_mse"||e==="mse_then_hls"?(z.errors=n,r===0):(z.errors=[{instancePath:s,schemaPath:"#/enum",keyword:"enum",params:{allowedValues:V.enum},message:"must be equal to one of the allowed values"}],!1)}z.evaluated={dynamicProps:!1,dynamicItems:!1};function _(e,{instancePath:s="",parentData:b,parentDataProperty:v,rootData:f=e,dynamicAnchors:d={}}={}){let n=null,r=0,u=_.evaluated;if(u.dynamicProps&&(u.props=void 0),u.dynamicItems&&(u.items=void 0),r===0)if(e&&typeof e=="object"&&!Array.isArray(e)){let y;if(e.version===void 0&&(y="version")||e.projection===void 0&&(y="projection")||e.mount===void 0&&(y="mount")||e.centerX===void 0&&(y="centerX")||e.centerY===void 0&&(y="centerY")||e.radius===void 0&&(y="radius")||e.fov===void 0&&(y="fov")||e.rotation===void 0&&(y="rotation")||e.defaultYaw===void 0&&(y="defaultYaw")||e.defaultTilt===void 0&&(y="defaultTilt")||e.defaultViewFov===void 0&&(y="defaultViewFov"))return _.errors=[{instancePath:s,schemaPath:"#/required",keyword:"required",params:{missingProperty:y},message:"must have required property '"+y+"'"}],!1;if(e.version!==void 0){let o=r;if(e.version!==1)return _.errors=[{instancePath:s+"/version",schemaPath:"#/properties/version/const",keyword:"const",params:{allowedValue:1},message:"must be equal to constant"}],!1;var c=o===r}else var c=!0;if(c){if(e.projection!==void 0){let o=r;if(e.projection!=="equidistant")return _.errors=[{instancePath:s+"/projection",schemaPath:"#/properties/projection/const",keyword:"const",params:{allowedValue:"equidistant"},message:"must be equal to constant"}],!1;var c=o===r}else var c=!0;if(c){if(e.mount!==void 0){let o=r;if(e.mount!=="ceiling")return _.errors=[{instancePath:s+"/mount",schemaPath:"#/properties/mount/const",keyword:"const",params:{allowedValue:"ceiling"},message:"must be equal to constant"}],!1;var c=o===r}else var c=!0;if(c){if(e.centerX!==void 0){let o=e.centerX,h=r;if(r===h)if(typeof o=="number"&&isFinite(o)){if(o>1||isNaN(o))return _.errors=[{instancePath:s+"/centerX",schemaPath:"#/properties/centerX/maximum",keyword:"maximum",params:{comparison:"<=",limit:1},message:"must be <= 1"}],!1;if(o<0||isNaN(o))return _.errors=[{instancePath:s+"/centerX",schemaPath:"#/properties/centerX/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1}else return _.errors=[{instancePath:s+"/centerX",schemaPath:"#/properties/centerX/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var c=h===r}else var c=!0;if(c){if(e.centerY!==void 0){let o=e.centerY,h=r;if(r===h)if(typeof o=="number"&&isFinite(o)){if(o>1||isNaN(o))return _.errors=[{instancePath:s+"/centerY",schemaPath:"#/properties/centerY/maximum",keyword:"maximum",params:{comparison:"<=",limit:1},message:"must be <= 1"}],!1;if(o<0||isNaN(o))return _.errors=[{instancePath:s+"/centerY",schemaPath:"#/properties/centerY/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1}else return _.errors=[{instancePath:s+"/centerY",schemaPath:"#/properties/centerY/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var c=h===r}else var c=!0;if(c){if(e.radius!==void 0){let o=e.radius,h=r;if(r===h)if(typeof o=="number"&&isFinite(o)){if(o>1||isNaN(o))return _.errors=[{instancePath:s+"/radius",schemaPath:"#/properties/radius/maximum",keyword:"maximum",params:{comparison:"<=",limit:1},message:"must be <= 1"}],!1;if(o<.05||isNaN(o))return _.errors=[{instancePath:s+"/radius",schemaPath:"#/properties/radius/minimum",keyword:"minimum",params:{comparison:">=",limit:.05},message:"must be >= 0.05"}],!1}else return _.errors=[{instancePath:s+"/radius",schemaPath:"#/properties/radius/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var c=h===r}else var c=!0;if(c){if(e.fov!==void 0){let o=e.fov,h=r;if(r===h)if(typeof o=="number"&&isFinite(o)){if(o>360||isNaN(o))return _.errors=[{instancePath:s+"/fov",schemaPath:"#/properties/fov/maximum",keyword:"maximum",params:{comparison:"<=",limit:360},message:"must be <= 360"}],!1;if(o<160||isNaN(o))return _.errors=[{instancePath:s+"/fov",schemaPath:"#/properties/fov/minimum",keyword:"minimum",params:{comparison:">=",limit:160},message:"must be >= 160"}],!1}else return _.errors=[{instancePath:s+"/fov",schemaPath:"#/properties/fov/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var c=h===r}else var c=!0;if(c){if(e.rotation!==void 0){let o=e.rotation,h=r;if(r===h)if(typeof o=="number"&&isFinite(o)){if(o>360||isNaN(o))return _.errors=[{instancePath:s+"/rotation",schemaPath:"#/properties/rotation/maximum",keyword:"maximum",params:{comparison:"<=",limit:360},message:"must be <= 360"}],!1;if(o<-360||isNaN(o))return _.errors=[{instancePath:s+"/rotation",schemaPath:"#/properties/rotation/minimum",keyword:"minimum",params:{comparison:">=",limit:-360},message:"must be >= -360"}],!1}else return _.errors=[{instancePath:s+"/rotation",schemaPath:"#/properties/rotation/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var c=h===r}else var c=!0;if(c){if(e.defaultYaw!==void 0){let o=e.defaultYaw,h=r;if(r===h)if(typeof o=="number"&&isFinite(o)){if(o>360||isNaN(o))return _.errors=[{instancePath:s+"/defaultYaw",schemaPath:"#/properties/defaultYaw/maximum",keyword:"maximum",params:{comparison:"<=",limit:360},message:"must be <= 360"}],!1;if(o<-360||isNaN(o))return _.errors=[{instancePath:s+"/defaultYaw",schemaPath:"#/properties/defaultYaw/minimum",keyword:"minimum",params:{comparison:">=",limit:-360},message:"must be >= -360"}],!1}else return _.errors=[{instancePath:s+"/defaultYaw",schemaPath:"#/properties/defaultYaw/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var c=h===r}else var c=!0;if(c){if(e.defaultTilt!==void 0){let o=e.defaultTilt,h=r;if(r===h)if(typeof o=="number"&&isFinite(o)){if(o>30||isNaN(o))return _.errors=[{instancePath:s+"/defaultTilt",schemaPath:"#/properties/defaultTilt/maximum",keyword:"maximum",params:{comparison:"<=",limit:30},message:"must be <= 30"}],!1;if(o<-90||isNaN(o))return _.errors=[{instancePath:s+"/defaultTilt",schemaPath:"#/properties/defaultTilt/minimum",keyword:"minimum",params:{comparison:">=",limit:-90},message:"must be >= -90"}],!1}else return _.errors=[{instancePath:s+"/defaultTilt",schemaPath:"#/properties/defaultTilt/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var c=h===r}else var c=!0;if(c)if(e.defaultViewFov!==void 0){let o=e.defaultViewFov,h=r;if(r===h)if(typeof o=="number"&&isFinite(o)){if(o>120||isNaN(o))return _.errors=[{instancePath:s+"/defaultViewFov",schemaPath:"#/properties/defaultViewFov/maximum",keyword:"maximum",params:{comparison:"<=",limit:120},message:"must be <= 120"}],!1;if(o<20||isNaN(o))return _.errors=[{instancePath:s+"/defaultViewFov",schemaPath:"#/properties/defaultViewFov/minimum",keyword:"minimum",params:{comparison:">=",limit:20},message:"must be >= 20"}],!1}else return _.errors=[{instancePath:s+"/defaultViewFov",schemaPath:"#/properties/defaultViewFov/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var c=h===r}else var c=!0}}}}}}}}}}else return _.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return _.errors=n,r===0}_.evaluated={props:{version:!0,projection:!0,mount:!0,centerX:!0,centerY:!0,radius:!0,fov:!0,rotation:!0,defaultYaw:!0,defaultTilt:!0,defaultViewFov:!0},dynamicProps:!1,dynamicItems:!1};function N(e,{instancePath:s="",parentData:b,parentDataProperty:v,rootData:f=e,dynamicAnchors:d={}}={}){let n=null,r=0,u=N.evaluated;if(u.dynamicProps&&(u.props=void 0),u.dynamicItems&&(u.items=void 0),r===0)if(typeof e=="string"){if(g(e)>1023)return N.errors=[{instancePath:s,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:1023},message:"must NOT have more than 1023 characters"}],!1;if(new TextEncoder().encode(e).byteLength>1023)return N.errors=[{instancePath:s,schemaPath:"#/x-lightnvr-max-bytes",keyword:"x-lightnvr-max-bytes",params:{},message:"must fit the UTF-8 byte limit"}],!1;if(e!==""){let y,o=!0;try{y=JSON.parse(e)}catch{o=!1}if(o){let h=r;_(y,{instancePath:s,parentData:e,parentDataProperty:v,rootData:f,dynamicAnchors:d})||(n=n===null?_.errors:n.concat(_.errors),r=n.length);var c=h===r;o=c}if(!o)return N.errors=[{instancePath:s,schemaPath:"#/contentSchema",keyword:"contentSchema",params:{},message:"must contain valid JSON matching contentSchema (or be empty)"}],!1}}else return N.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return N.errors=n,r===0}N.evaluated={dynamicProps:!1,dynamicItems:!1};function p(e,{instancePath:s="",parentData:b,parentDataProperty:v,rootData:f=e,dynamicAnchors:d={}}={}){let n=null,r=0,u=p.evaluated;if(u.dynamicProps&&(u.props=void 0),u.dynamicItems&&(u.items=void 0),!(typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)))return p.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;if(r===0&&typeof e=="number"&&isFinite(e)){if(e>2147483647||isNaN(e))return p.errors=[{instancePath:s,schemaPath:"#/maximum",keyword:"maximum",params:{comparison:"<=",limit:2147483647},message:"must be <= 2147483647"}],!1;if(e<-2147483648||isNaN(e))return p.errors=[{instancePath:s,schemaPath:"#/minimum",keyword:"minimum",params:{comparison:">=",limit:-2147483648},message:"must be >= -2147483648"}],!1}return p.errors=n,r===0}p.evaluated={dynamicProps:!1,dynamicItems:!1};var A={type:"integer",enum:[0,1],$id:"urn:lightnvr:contract:StreamProtocol"};function D(e,{instancePath:s="",parentData:b,parentDataProperty:v,rootData:f=e,dynamicAnchors:d={}}={}){let n=null,r=0,u=D.evaluated;return u.dynamicProps&&(u.props=void 0),u.dynamicItems&&(u.items=void 0),typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)?e===0||e===1?(D.errors=n,r===0):(D.errors=[{instancePath:s,schemaPath:"#/enum",keyword:"enum",params:{allowedValues:A.enum},message:"must be equal to one of the allowed values"}],!1):(D.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1)}D.evaluated={dynamicProps:!1,dynamicItems:!1};function P(e,{instancePath:s="",parentData:b,parentDataProperty:v,rootData:f=e,dynamicAnchors:d={}}={}){let n=null,r=0,u=P.evaluated;if(u.dynamicProps&&(u.props=void 0),u.dynamicItems&&(u.items=void 0),!(typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)))return P.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;if(r===0&&typeof e=="number"&&isFinite(e)){if(e>365||isNaN(e))return P.errors=[{instancePath:s,schemaPath:"#/maximum",keyword:"maximum",params:{comparison:"<=",limit:365},message:"must be <= 365"}],!1;if(e<-1||isNaN(e))return P.errors=[{instancePath:s,schemaPath:"#/minimum",keyword:"minimum",params:{comparison:">=",limit:-1},message:"must be >= -1"}],!1}return P.errors=n,r===0}P.evaluated={dynamicProps:!1,dynamicItems:!1};function w(e,{instancePath:s="",parentData:b,parentDataProperty:v,rootData:f=e,dynamicAnchors:d={}}={}){let n=null,r=0,u=w.evaluated;if(u.dynamicProps&&(u.props=void 0),u.dynamicItems&&(u.items=void 0),r===0)if(Array.isArray(e)){if(e.length>168)return w.errors=[{instancePath:s,schemaPath:"#/maxItems",keyword:"maxItems",params:{limit:168},message:"must NOT have more than 168 items"}],!1;if(e.length<168)return w.errors=[{instancePath:s,schemaPath:"#/minItems",keyword:"minItems",params:{limit:168},message:"must NOT have fewer than 168 items"}],!1;{var c=!0;let y=e.length;for(let o=0;o511)return l.errors=[{instancePath:s+"/url",schemaPath:"#/properties/url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return l.errors=[{instancePath:s+"/url",schemaPath:"#/properties/url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var t=m===r}else var t=!0;if(t){if(e.enabled!==void 0){let i=r;if(typeof e.enabled!="boolean")return l.errors=[{instancePath:s+"/enabled",schemaPath:"#/properties/enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var t=i===r}else var t=!0;if(t){if(e.streaming_enabled!==void 0){let i=r;if(typeof e.streaming_enabled!="boolean")return l.errors=[{instancePath:s+"/streaming_enabled",schemaPath:"#/properties/streaming_enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var t=i===r}else var t=!0;if(t){if(e.playback_transport!==void 0){let i=r;z(e.playback_transport,{instancePath:s+"/playback_transport",parentData:e,parentDataProperty:"playback_transport",rootData:f,dynamicAnchors:d})||(n=n===null?z.errors:n.concat(z.errors),r=n.length);var t=i===r}else var t=!0;if(t){if(e.eptz_config!==void 0){let i=r;N(e.eptz_config,{instancePath:s+"/eptz_config",parentData:e,parentDataProperty:"eptz_config",rootData:f,dynamicAnchors:d})||(n=n===null?N.errors:n.concat(N.errors),r=n.length);var t=i===r}else var t=!0;if(t){if(e.width!==void 0){let i=r;p(e.width,{instancePath:s+"/width",parentData:e,parentDataProperty:"width",rootData:f,dynamicAnchors:d})||(n=n===null?p.errors:n.concat(p.errors),r=n.length);var t=i===r}else var t=!0;if(t){if(e.height!==void 0){let i=r;p(e.height,{instancePath:s+"/height",parentData:e,parentDataProperty:"height",rootData:f,dynamicAnchors:d})||(n=n===null?p.errors:n.concat(p.errors),r=n.length);var t=i===r}else var t=!0;if(t){if(e.fps!==void 0){let i=r;p(e.fps,{instancePath:s+"/fps",parentData:e,parentDataProperty:"fps",rootData:f,dynamicAnchors:d})||(n=n===null?p.errors:n.concat(p.errors),r=n.length);var t=i===r}else var t=!0;if(t){if(e.codec!==void 0){let i=e.codec,m=r;if(r===m)if(typeof i=="string"){if(g(i)>15)return l.errors=[{instancePath:s+"/codec",schemaPath:"#/properties/codec/maxLength",keyword:"maxLength",params:{limit:15},message:"must NOT have more than 15 characters"}],!1}else return l.errors=[{instancePath:s+"/codec",schemaPath:"#/properties/codec/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var t=m===r}else var t=!0;if(t){if(e.priority!==void 0){let i=r;p(e.priority,{instancePath:s+"/priority",parentData:e,parentDataProperty:"priority",rootData:f,dynamicAnchors:d})||(n=n===null?p.errors:n.concat(p.errors),r=n.length);var t=i===r}else var t=!0;if(t){if(e.record!==void 0){let i=r;if(typeof e.record!="boolean")return l.errors=[{instancePath:s+"/record",schemaPath:"#/properties/record/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var t=i===r}else var t=!0;if(t){if(e.segment_duration!==void 0){let i=r;p(e.segment_duration,{instancePath:s+"/segment_duration",parentData:e,parentDataProperty:"segment_duration",rootData:f,dynamicAnchors:d})||(n=n===null?p.errors:n.concat(p.errors),r=n.length);var t=i===r}else var t=!0;if(t){if(e.detection_based_recording!==void 0){let i=r;if(typeof e.detection_based_recording!="boolean")return l.errors=[{instancePath:s+"/detection_based_recording",schemaPath:"#/properties/detection_based_recording/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var t=i===r}else var t=!0;if(t){if(e.detection_model!==void 0){let i=e.detection_model,m=r;if(r===m)if(typeof i=="string"){if(g(i)>511)return l.errors=[{instancePath:s+"/detection_model",schemaPath:"#/properties/detection_model/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return l.errors=[{instancePath:s+"/detection_model",schemaPath:"#/properties/detection_model/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var t=m===r}else var t=!0;if(t){if(e.detection_threshold!==void 0){let i=e.detection_threshold,m=r;if(!(typeof i=="number"&&!(i%1)&&!isNaN(i)&&isFinite(i)))return l.errors=[{instancePath:s+"/detection_threshold",schemaPath:"#/properties/detection_threshold/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;var t=m===r}else var t=!0;if(t){if(e.detection_interval!==void 0){let i=r;p(e.detection_interval,{instancePath:s+"/detection_interval",parentData:e,parentDataProperty:"detection_interval",rootData:f,dynamicAnchors:d})||(n=n===null?p.errors:n.concat(p.errors),r=n.length);var t=i===r}else var t=!0;if(t){if(e.pre_detection_buffer!==void 0){let i=r;p(e.pre_detection_buffer,{instancePath:s+"/pre_detection_buffer",parentData:e,parentDataProperty:"pre_detection_buffer",rootData:f,dynamicAnchors:d})||(n=n===null?p.errors:n.concat(p.errors),r=n.length);var t=i===r}else var t=!0;if(t){if(e.post_detection_buffer!==void 0){let i=r;p(e.post_detection_buffer,{instancePath:s+"/post_detection_buffer",parentData:e,parentDataProperty:"post_detection_buffer",rootData:f,dynamicAnchors:d})||(n=n===null?p.errors:n.concat(p.errors),r=n.length);var t=i===r}else var t=!0;if(t){if(e.detection_object_filter!==void 0){let i=e.detection_object_filter,m=r;if(r===m)if(typeof i=="string"){if(g(i)>15)return l.errors=[{instancePath:s+"/detection_object_filter",schemaPath:"#/properties/detection_object_filter/maxLength",keyword:"maxLength",params:{limit:15},message:"must NOT have more than 15 characters"}],!1}else return l.errors=[{instancePath:s+"/detection_object_filter",schemaPath:"#/properties/detection_object_filter/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var t=m===r}else var t=!0;if(t){if(e.detection_object_filter_list!==void 0){let i=e.detection_object_filter_list,m=r;if(r===m)if(typeof i=="string"){if(g(i)>255)return l.errors=[{instancePath:s+"/detection_object_filter_list",schemaPath:"#/properties/detection_object_filter_list/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1}else return l.errors=[{instancePath:s+"/detection_object_filter_list",schemaPath:"#/properties/detection_object_filter_list/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var t=m===r}else var t=!0;if(t){if(e.protocol!==void 0){let i=r;D(e.protocol,{instancePath:s+"/protocol",parentData:e,parentDataProperty:"protocol",rootData:f,dynamicAnchors:d})||(n=n===null?D.errors:n.concat(D.errors),r=n.length);var t=i===r}else var t=!0;if(t){if(e.record_audio!==void 0){let i=r;if(typeof e.record_audio!="boolean")return l.errors=[{instancePath:s+"/record_audio",schemaPath:"#/properties/record_audio/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var t=i===r}else var t=!0;if(t){if(e.audio_voice_enhancement!==void 0){let i=r;if(typeof e.audio_voice_enhancement!="boolean")return l.errors=[{instancePath:s+"/audio_voice_enhancement",schemaPath:"#/properties/audio_voice_enhancement/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var t=i===r}else var t=!0;if(t){if(e.isOnvif!==void 0){let i=r;if(typeof e.isOnvif!="boolean")return l.errors=[{instancePath:s+"/isOnvif",schemaPath:"#/properties/isOnvif/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var t=i===r}else var t=!0;if(t){if(e.backchannel_enabled!==void 0){let i=r;if(typeof e.backchannel_enabled!="boolean")return l.errors=[{instancePath:s+"/backchannel_enabled",schemaPath:"#/properties/backchannel_enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var t=i===r}else var t=!0;if(t){if(e.retention_days!==void 0){let i=r;P(e.retention_days,{instancePath:s+"/retention_days",parentData:e,parentDataProperty:"retention_days",rootData:f,dynamicAnchors:d})||(n=n===null?P.errors:n.concat(P.errors),r=n.length);var t=i===r}else var t=!0;if(t){if(e.detection_retention_days!==void 0){let i=r;P(e.detection_retention_days,{instancePath:s+"/detection_retention_days",parentData:e,parentDataProperty:"detection_retention_days",rootData:f,dynamicAnchors:d})||(n=n===null?P.errors:n.concat(P.errors),r=n.length);var t=i===r}else var t=!0;if(t){if(e.max_storage_mb!==void 0){let i=r;p(e.max_storage_mb,{instancePath:s+"/max_storage_mb",parentData:e,parentDataProperty:"max_storage_mb",rootData:f,dynamicAnchors:d})||(n=n===null?p.errors:n.concat(p.errors),r=n.length);var t=i===r}else var t=!0;if(t){if(e.tier_critical_multiplier!==void 0){let i=e.tier_critical_multiplier,m=r;if(!(typeof i=="number"&&isFinite(i)))return l.errors=[{instancePath:s+"/tier_critical_multiplier",schemaPath:"#/properties/tier_critical_multiplier/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var t=m===r}else var t=!0;if(t){if(e.tier_important_multiplier!==void 0){let i=e.tier_important_multiplier,m=r;if(!(typeof i=="number"&&isFinite(i)))return l.errors=[{instancePath:s+"/tier_important_multiplier",schemaPath:"#/properties/tier_important_multiplier/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var t=m===r}else var t=!0;if(t){if(e.tier_ephemeral_multiplier!==void 0){let i=e.tier_ephemeral_multiplier,m=r;if(!(typeof i=="number"&&isFinite(i)))return l.errors=[{instancePath:s+"/tier_ephemeral_multiplier",schemaPath:"#/properties/tier_ephemeral_multiplier/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var t=m===r}else var t=!0;if(t){if(e.storage_priority!==void 0){let i=r;p(e.storage_priority,{instancePath:s+"/storage_priority",parentData:e,parentDataProperty:"storage_priority",rootData:f,dynamicAnchors:d})||(n=n===null?p.errors:n.concat(p.errors),r=n.length);var t=i===r}else var t=!0;if(t){if(e.ptz_enabled!==void 0){let i=r;if(typeof e.ptz_enabled!="boolean")return l.errors=[{instancePath:s+"/ptz_enabled",schemaPath:"#/properties/ptz_enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var t=i===r}else var t=!0;if(t){if(e.ptz_max_x!==void 0){let i=r;p(e.ptz_max_x,{instancePath:s+"/ptz_max_x",parentData:e,parentDataProperty:"ptz_max_x",rootData:f,dynamicAnchors:d})||(n=n===null?p.errors:n.concat(p.errors),r=n.length);var t=i===r}else var t=!0;if(t){if(e.ptz_max_y!==void 0){let i=r;p(e.ptz_max_y,{instancePath:s+"/ptz_max_y",parentData:e,parentDataProperty:"ptz_max_y",rootData:f,dynamicAnchors:d})||(n=n===null?p.errors:n.concat(p.errors),r=n.length);var t=i===r}else var t=!0;if(t){if(e.ptz_max_z!==void 0){let i=r;p(e.ptz_max_z,{instancePath:s+"/ptz_max_z",parentData:e,parentDataProperty:"ptz_max_z",rootData:f,dynamicAnchors:d})||(n=n===null?p.errors:n.concat(p.errors),r=n.length);var t=i===r}else var t=!0;if(t){if(e.ptz_has_home!==void 0){let i=r;if(typeof e.ptz_has_home!="boolean")return l.errors=[{instancePath:s+"/ptz_has_home",schemaPath:"#/properties/ptz_has_home/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var t=i===r}else var t=!0;if(t){if(e.onvif_username!==void 0){let i=e.onvif_username,m=r;if(r===m)if(typeof i=="string"){if(g(i)>63)return l.errors=[{instancePath:s+"/onvif_username",schemaPath:"#/properties/onvif_username/maxLength",keyword:"maxLength",params:{limit:63},message:"must NOT have more than 63 characters"}],!1}else return l.errors=[{instancePath:s+"/onvif_username",schemaPath:"#/properties/onvif_username/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var t=m===r}else var t=!0;if(t){if(e.onvif_password!==void 0){let i=e.onvif_password,m=r;if(r===m)if(typeof i=="string"){if(g(i)>63)return l.errors=[{instancePath:s+"/onvif_password",schemaPath:"#/properties/onvif_password/maxLength",keyword:"maxLength",params:{limit:63},message:"must NOT have more than 63 characters"}],!1}else return l.errors=[{instancePath:s+"/onvif_password",schemaPath:"#/properties/onvif_password/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var t=m===r}else var t=!0;if(t){if(e.onvif_profile!==void 0){let i=e.onvif_profile,m=r;if(r===m)if(typeof i=="string"){if(g(i)>63)return l.errors=[{instancePath:s+"/onvif_profile",schemaPath:"#/properties/onvif_profile/maxLength",keyword:"maxLength",params:{limit:63},message:"must NOT have more than 63 characters"}],!1}else return l.errors=[{instancePath:s+"/onvif_profile",schemaPath:"#/properties/onvif_profile/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var t=m===r}else var t=!0;if(t){if(e.onvif_port!==void 0){let i=r;p(e.onvif_port,{instancePath:s+"/onvif_port",parentData:e,parentDataProperty:"onvif_port",rootData:f,dynamicAnchors:d})||(n=n===null?p.errors:n.concat(p.errors),r=n.length);var t=i===r}else var t=!0;if(t){if(e.record_on_schedule!==void 0){let i=r;if(typeof e.record_on_schedule!="boolean")return l.errors=[{instancePath:s+"/record_on_schedule",schemaPath:"#/properties/record_on_schedule/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var t=i===r}else var t=!0;if(t){if(e.recording_schedule!==void 0){let i=r;w(e.recording_schedule,{instancePath:s+"/recording_schedule",parentData:e,parentDataProperty:"recording_schedule",rootData:f,dynamicAnchors:d})||(n=n===null?w.errors:n.concat(w.errors),r=n.length);var t=i===r}else var t=!0;if(t){if(e.detection_record_on_schedule!==void 0){let i=r;if(typeof e.detection_record_on_schedule!="boolean")return l.errors=[{instancePath:s+"/detection_record_on_schedule",schemaPath:"#/properties/detection_record_on_schedule/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var t=i===r}else var t=!0;if(t){if(e.detection_recording_schedule!==void 0){let i=r;w(e.detection_recording_schedule,{instancePath:s+"/detection_recording_schedule",parentData:e,parentDataProperty:"detection_recording_schedule",rootData:f,dynamicAnchors:d})||(n=n===null?w.errors:n.concat(w.errors),r=n.length);var t=i===r}else var t=!0;if(t){if(e.tags!==void 0){let i=e.tags,m=r;if(r===m)if(typeof i=="string"){if(g(i)>255)return l.errors=[{instancePath:s+"/tags",schemaPath:"#/properties/tags/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1}else return l.errors=[{instancePath:s+"/tags",schemaPath:"#/properties/tags/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var t=m===r}else var t=!0;if(t){if(e.admin_url!==void 0){let i=e.admin_url,m=r;if(r===m)if(typeof i=="string"){if(g(i)>511)return l.errors=[{instancePath:s+"/admin_url",schemaPath:"#/properties/admin_url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return l.errors=[{instancePath:s+"/admin_url",schemaPath:"#/properties/admin_url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var t=m===r}else var t=!0;if(t){if(e.privacy_mode!==void 0){let i=r;if(typeof e.privacy_mode!="boolean")return l.errors=[{instancePath:s+"/privacy_mode",schemaPath:"#/properties/privacy_mode/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var t=i===r}else var t=!0;if(t){if(e.can_control_privacy!==void 0){let i=r;if(typeof e.can_control_privacy!="boolean")return l.errors=[{instancePath:s+"/can_control_privacy",schemaPath:"#/properties/can_control_privacy/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var t=i===r}else var t=!0;if(t){if(e.motion_trigger_source!==void 0){let i=e.motion_trigger_source,m=r;if(r===m)if(typeof i=="string"){if(g(i)>255)return l.errors=[{instancePath:s+"/motion_trigger_source",schemaPath:"#/properties/motion_trigger_source/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1}else return l.errors=[{instancePath:s+"/motion_trigger_source",schemaPath:"#/properties/motion_trigger_source/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var t=m===r}else var t=!0;if(t){if(e.go2rtc_source_override!==void 0){let i=e.go2rtc_source_override,m=r;if(r===m)if(typeof i=="string"){if(g(i)>8191)return l.errors=[{instancePath:s+"/go2rtc_source_override",schemaPath:"#/properties/go2rtc_source_override/maxLength",keyword:"maxLength",params:{limit:8191},message:"must NOT have more than 8191 characters"}],!1}else return l.errors=[{instancePath:s+"/go2rtc_source_override",schemaPath:"#/properties/go2rtc_source_override/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var t=m===r}else var t=!0;if(t){if(e.sub_stream_url!==void 0){let i=e.sub_stream_url,m=r;if(r===m)if(typeof i=="string"){if(g(i)>511)return l.errors=[{instancePath:s+"/sub_stream_url",schemaPath:"#/properties/sub_stream_url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return l.errors=[{instancePath:s+"/sub_stream_url",schemaPath:"#/properties/sub_stream_url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var t=m===r}else var t=!0;if(t){if(e.has_sub_stream!==void 0){let i=r;if(typeof e.has_sub_stream!="boolean")return l.errors=[{instancePath:s+"/has_sub_stream",schemaPath:"#/properties/has_sub_stream/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var t=i===r}else var t=!0;if(t){if(e.detection_url!==void 0){let i=e.detection_url,m=r;if(r===m)if(typeof i=="string"){if(g(i)>511)return l.errors=[{instancePath:s+"/detection_url",schemaPath:"#/properties/detection_url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return l.errors=[{instancePath:s+"/detection_url",schemaPath:"#/properties/detection_url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var t=m===r}else var t=!0;if(t){if(e.publish_url!==void 0){let i=e.publish_url,m=r;if(r===m)if(typeof i=="string"){if(g(i)>511)return l.errors=[{instancePath:s+"/publish_url",schemaPath:"#/properties/publish_url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return l.errors=[{instancePath:s+"/publish_url",schemaPath:"#/properties/publish_url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var t=m===r}else var t=!0;if(t){if(e.status!==void 0){let i=r;L(e.status,{instancePath:s+"/status",parentData:e,parentDataProperty:"status",rootData:f,dynamicAnchors:d})||(n=n===null?L.errors:n.concat(L.errors),r=n.length);var t=i===r}else var t=!0;if(t){if(e.error_message!==void 0){let i=e.error_message,m=r;if(r===m)if(typeof i=="string"){if(g(i)>255)return l.errors=[{instancePath:s+"/error_message",schemaPath:"#/properties/error_message/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1;if(g(i)<1)return l.errors=[{instancePath:s+"/error_message",schemaPath:"#/properties/error_message/minLength",keyword:"minLength",params:{limit:1},message:"must NOT have fewer than 1 characters"}],!1}else return l.errors=[{instancePath:s+"/error_message",schemaPath:"#/properties/error_message/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var t=m===r}else var t=!0;if(t)if(e.go2rtc_hls_available!==void 0){let i=r;if(typeof e.go2rtc_hls_available!="boolean")return l.errors=[{instancePath:s+"/go2rtc_hls_available",schemaPath:"#/properties/go2rtc_hls_available/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var t=i===r}else var t=!0}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}else return l.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return l.errors=n,r===0}l.evaluated={props:{camera_uuid:!0,location_uuid:!0,name:!0,url:!0,enabled:!0,streaming_enabled:!0,playback_transport:!0,eptz_config:!0,width:!0,height:!0,fps:!0,codec:!0,priority:!0,record:!0,segment_duration:!0,detection_based_recording:!0,detection_model:!0,detection_threshold:!0,detection_interval:!0,pre_detection_buffer:!0,post_detection_buffer:!0,detection_object_filter:!0,detection_object_filter_list:!0,protocol:!0,record_audio:!0,audio_voice_enhancement:!0,isOnvif:!0,backchannel_enabled:!0,retention_days:!0,detection_retention_days:!0,max_storage_mb:!0,tier_critical_multiplier:!0,tier_important_multiplier:!0,tier_ephemeral_multiplier:!0,storage_priority:!0,ptz_enabled:!0,ptz_max_x:!0,ptz_max_y:!0,ptz_max_z:!0,ptz_has_home:!0,onvif_username:!0,onvif_password:!0,onvif_profile:!0,onvif_port:!0,record_on_schedule:!0,recording_schedule:!0,detection_record_on_schedule:!0,detection_recording_schedule:!0,tags:!0,admin_url:!0,privacy_mode:!0,can_control_privacy:!0,motion_trigger_source:!0,go2rtc_source_override:!0,sub_stream_url:!0,has_sub_stream:!0,detection_url:!0,publish_url:!0,status:!0,error_message:!0,go2rtc_hls_available:!0},dynamicProps:!1,dynamicItems:!1};export{U as validateStreamDetailBase}; diff --git a/web/js/api/generated/validators/StreamDetailConfigurable.d.mts b/web/js/api/generated/validators/StreamDetailConfigurable.d.mts new file mode 100644 index 000000000..52bb031c3 --- /dev/null +++ b/web/js/api/generated/validators/StreamDetailConfigurable.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateStreamDetailConfigurable } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/StreamDetailConfigurable.mjs b/web/js/api/generated/validators/StreamDetailConfigurable.mjs new file mode 100644 index 000000000..171cf1947 --- /dev/null +++ b/web/js/api/generated/validators/StreamDetailConfigurable.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var U=(e,n)=>()=>{try{return n||e((n={exports:{}}).exports,n),n.exports}catch(w){throw n=0,w}};var X=U(E=>{"use strict";Object.defineProperty(E,"__esModule",{value:!0});function V(e){let n=e.length,w=0,b=0,c;for(;b=55296&&c<=56319&&b36)return z.errors=[{instancePath:n,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:36},message:"must NOT have more than 36 characters"}],!1;if(!R.test(e))return z.errors=[{instancePath:n,schemaPath:"#/pattern",keyword:"pattern",params:{pattern:"^(|[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})$"},message:'must match pattern "^(|[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})$"'}],!1}else return z.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return z.errors=t,r===0}z.evaluated={dynamicProps:!1,dynamicItems:!1};function D(e,{instancePath:n="",parentData:w,parentDataProperty:b,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=D.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(typeof e=="string"){if(v(e)>255)return D.errors=[{instancePath:n,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1;if(v(e)<1)return D.errors=[{instancePath:n,schemaPath:"#/minLength",keyword:"minLength",params:{limit:1},message:"must NOT have fewer than 1 characters"}],!1;if(new TextEncoder().encode(e).byteLength>255)return D.errors=[{instancePath:n,schemaPath:"#/x-lightnvr-max-bytes",keyword:"x-lightnvr-max-bytes",params:{},message:"must fit the UTF-8 byte limit"}],!1}else return D.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return D.errors=t,r===0}D.evaluated={dynamicProps:!1,dynamicItems:!1};var Z={type:"string",enum:["auto","webrtc_only","mse_only","hls_only","webrtc_then_mse","mse_then_hls"],$id:"urn:lightnvr:contract:PlaybackTransport"};function F(e,{instancePath:n="",parentData:w,parentDataProperty:b,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=F.evaluated;return o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e!="string"?(F.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1):e==="auto"||e==="webrtc_only"||e==="mse_only"||e==="hls_only"||e==="webrtc_then_mse"||e==="mse_then_hls"?(F.errors=t,r===0):(F.errors=[{instancePath:n,schemaPath:"#/enum",keyword:"enum",params:{allowedValues:Z.enum},message:"must be equal to one of the allowed values"}],!1)}F.evaluated={dynamicProps:!1,dynamicItems:!1};function h(e,{instancePath:n="",parentData:w,parentDataProperty:b,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=h.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(e&&typeof e=="object"&&!Array.isArray(e)){let y;if(e.version===void 0&&(y="version")||e.projection===void 0&&(y="projection")||e.mount===void 0&&(y="mount")||e.centerX===void 0&&(y="centerX")||e.centerY===void 0&&(y="centerY")||e.radius===void 0&&(y="radius")||e.fov===void 0&&(y="fov")||e.rotation===void 0&&(y="rotation")||e.defaultYaw===void 0&&(y="defaultYaw")||e.defaultTilt===void 0&&(y="defaultTilt")||e.defaultViewFov===void 0&&(y="defaultViewFov"))return h.errors=[{instancePath:n,schemaPath:"#/required",keyword:"required",params:{missingProperty:y},message:"must have required property '"+y+"'"}],!1;if(e.version!==void 0){let a=r;if(e.version!==1)return h.errors=[{instancePath:n+"/version",schemaPath:"#/properties/version/const",keyword:"const",params:{allowedValue:1},message:"must be equal to constant"}],!1;var p=a===r}else var p=!0;if(p){if(e.projection!==void 0){let a=r;if(e.projection!=="equidistant")return h.errors=[{instancePath:n+"/projection",schemaPath:"#/properties/projection/const",keyword:"const",params:{allowedValue:"equidistant"},message:"must be equal to constant"}],!1;var p=a===r}else var p=!0;if(p){if(e.mount!==void 0){let a=r;if(e.mount!=="ceiling")return h.errors=[{instancePath:n+"/mount",schemaPath:"#/properties/mount/const",keyword:"const",params:{allowedValue:"ceiling"},message:"must be equal to constant"}],!1;var p=a===r}else var p=!0;if(p){if(e.centerX!==void 0){let a=e.centerX,_=r;if(r===_)if(typeof a=="number"&&isFinite(a)){if(a>1||isNaN(a))return h.errors=[{instancePath:n+"/centerX",schemaPath:"#/properties/centerX/maximum",keyword:"maximum",params:{comparison:"<=",limit:1},message:"must be <= 1"}],!1;if(a<0||isNaN(a))return h.errors=[{instancePath:n+"/centerX",schemaPath:"#/properties/centerX/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1}else return h.errors=[{instancePath:n+"/centerX",schemaPath:"#/properties/centerX/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var p=_===r}else var p=!0;if(p){if(e.centerY!==void 0){let a=e.centerY,_=r;if(r===_)if(typeof a=="number"&&isFinite(a)){if(a>1||isNaN(a))return h.errors=[{instancePath:n+"/centerY",schemaPath:"#/properties/centerY/maximum",keyword:"maximum",params:{comparison:"<=",limit:1},message:"must be <= 1"}],!1;if(a<0||isNaN(a))return h.errors=[{instancePath:n+"/centerY",schemaPath:"#/properties/centerY/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1}else return h.errors=[{instancePath:n+"/centerY",schemaPath:"#/properties/centerY/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var p=_===r}else var p=!0;if(p){if(e.radius!==void 0){let a=e.radius,_=r;if(r===_)if(typeof a=="number"&&isFinite(a)){if(a>1||isNaN(a))return h.errors=[{instancePath:n+"/radius",schemaPath:"#/properties/radius/maximum",keyword:"maximum",params:{comparison:"<=",limit:1},message:"must be <= 1"}],!1;if(a<.05||isNaN(a))return h.errors=[{instancePath:n+"/radius",schemaPath:"#/properties/radius/minimum",keyword:"minimum",params:{comparison:">=",limit:.05},message:"must be >= 0.05"}],!1}else return h.errors=[{instancePath:n+"/radius",schemaPath:"#/properties/radius/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var p=_===r}else var p=!0;if(p){if(e.fov!==void 0){let a=e.fov,_=r;if(r===_)if(typeof a=="number"&&isFinite(a)){if(a>360||isNaN(a))return h.errors=[{instancePath:n+"/fov",schemaPath:"#/properties/fov/maximum",keyword:"maximum",params:{comparison:"<=",limit:360},message:"must be <= 360"}],!1;if(a<160||isNaN(a))return h.errors=[{instancePath:n+"/fov",schemaPath:"#/properties/fov/minimum",keyword:"minimum",params:{comparison:">=",limit:160},message:"must be >= 160"}],!1}else return h.errors=[{instancePath:n+"/fov",schemaPath:"#/properties/fov/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var p=_===r}else var p=!0;if(p){if(e.rotation!==void 0){let a=e.rotation,_=r;if(r===_)if(typeof a=="number"&&isFinite(a)){if(a>360||isNaN(a))return h.errors=[{instancePath:n+"/rotation",schemaPath:"#/properties/rotation/maximum",keyword:"maximum",params:{comparison:"<=",limit:360},message:"must be <= 360"}],!1;if(a<-360||isNaN(a))return h.errors=[{instancePath:n+"/rotation",schemaPath:"#/properties/rotation/minimum",keyword:"minimum",params:{comparison:">=",limit:-360},message:"must be >= -360"}],!1}else return h.errors=[{instancePath:n+"/rotation",schemaPath:"#/properties/rotation/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var p=_===r}else var p=!0;if(p){if(e.defaultYaw!==void 0){let a=e.defaultYaw,_=r;if(r===_)if(typeof a=="number"&&isFinite(a)){if(a>360||isNaN(a))return h.errors=[{instancePath:n+"/defaultYaw",schemaPath:"#/properties/defaultYaw/maximum",keyword:"maximum",params:{comparison:"<=",limit:360},message:"must be <= 360"}],!1;if(a<-360||isNaN(a))return h.errors=[{instancePath:n+"/defaultYaw",schemaPath:"#/properties/defaultYaw/minimum",keyword:"minimum",params:{comparison:">=",limit:-360},message:"must be >= -360"}],!1}else return h.errors=[{instancePath:n+"/defaultYaw",schemaPath:"#/properties/defaultYaw/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var p=_===r}else var p=!0;if(p){if(e.defaultTilt!==void 0){let a=e.defaultTilt,_=r;if(r===_)if(typeof a=="number"&&isFinite(a)){if(a>30||isNaN(a))return h.errors=[{instancePath:n+"/defaultTilt",schemaPath:"#/properties/defaultTilt/maximum",keyword:"maximum",params:{comparison:"<=",limit:30},message:"must be <= 30"}],!1;if(a<-90||isNaN(a))return h.errors=[{instancePath:n+"/defaultTilt",schemaPath:"#/properties/defaultTilt/minimum",keyword:"minimum",params:{comparison:">=",limit:-90},message:"must be >= -90"}],!1}else return h.errors=[{instancePath:n+"/defaultTilt",schemaPath:"#/properties/defaultTilt/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var p=_===r}else var p=!0;if(p)if(e.defaultViewFov!==void 0){let a=e.defaultViewFov,_=r;if(r===_)if(typeof a=="number"&&isFinite(a)){if(a>120||isNaN(a))return h.errors=[{instancePath:n+"/defaultViewFov",schemaPath:"#/properties/defaultViewFov/maximum",keyword:"maximum",params:{comparison:"<=",limit:120},message:"must be <= 120"}],!1;if(a<20||isNaN(a))return h.errors=[{instancePath:n+"/defaultViewFov",schemaPath:"#/properties/defaultViewFov/minimum",keyword:"minimum",params:{comparison:">=",limit:20},message:"must be >= 20"}],!1}else return h.errors=[{instancePath:n+"/defaultViewFov",schemaPath:"#/properties/defaultViewFov/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var p=_===r}else var p=!0}}}}}}}}}}else return h.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return h.errors=t,r===0}h.evaluated={props:{version:!0,projection:!0,mount:!0,centerX:!0,centerY:!0,radius:!0,fov:!0,rotation:!0,defaultYaw:!0,defaultTilt:!0,defaultViewFov:!0},dynamicProps:!1,dynamicItems:!1};function L(e,{instancePath:n="",parentData:w,parentDataProperty:b,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=L.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(typeof e=="string"){if(v(e)>1023)return L.errors=[{instancePath:n,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:1023},message:"must NOT have more than 1023 characters"}],!1;if(new TextEncoder().encode(e).byteLength>1023)return L.errors=[{instancePath:n,schemaPath:"#/x-lightnvr-max-bytes",keyword:"x-lightnvr-max-bytes",params:{},message:"must fit the UTF-8 byte limit"}],!1;if(e!==""){let y,a=!0;try{y=JSON.parse(e)}catch{a=!1}if(a){let _=r;h(y,{instancePath:n,parentData:e,parentDataProperty:b,rootData:c,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var p=_===r;a=p}if(!a)return L.errors=[{instancePath:n,schemaPath:"#/contentSchema",keyword:"contentSchema",params:{},message:"must contain valid JSON matching contentSchema (or be empty)"}],!1}}else return L.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return L.errors=t,r===0}L.evaluated={dynamicProps:!1,dynamicItems:!1};function f(e,{instancePath:n="",parentData:w,parentDataProperty:b,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=f.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),!(typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)))return f.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;if(r===0&&typeof e=="number"&&isFinite(e)){if(e>2147483647||isNaN(e))return f.errors=[{instancePath:n,schemaPath:"#/maximum",keyword:"maximum",params:{comparison:"<=",limit:2147483647},message:"must be <= 2147483647"}],!1;if(e<-2147483648||isNaN(e))return f.errors=[{instancePath:n,schemaPath:"#/minimum",keyword:"minimum",params:{comparison:">=",limit:-2147483648},message:"must be >= -2147483648"}],!1}return f.errors=t,r===0}f.evaluated={dynamicProps:!1,dynamicItems:!1};var W={type:"integer",enum:[0,1],$id:"urn:lightnvr:contract:StreamProtocol"};function I(e,{instancePath:n="",parentData:w,parentDataProperty:b,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=I.evaluated;return o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)?e===0||e===1?(I.errors=t,r===0):(I.errors=[{instancePath:n,schemaPath:"#/enum",keyword:"enum",params:{allowedValues:W.enum},message:"must be equal to one of the allowed values"}],!1):(I.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1)}I.evaluated={dynamicProps:!1,dynamicItems:!1};function N(e,{instancePath:n="",parentData:w,parentDataProperty:b,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=N.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),!(typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)))return N.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;if(r===0&&typeof e=="number"&&isFinite(e)){if(e>365||isNaN(e))return N.errors=[{instancePath:n,schemaPath:"#/maximum",keyword:"maximum",params:{comparison:"<=",limit:365},message:"must be <= 365"}],!1;if(e<-1||isNaN(e))return N.errors=[{instancePath:n,schemaPath:"#/minimum",keyword:"minimum",params:{comparison:">=",limit:-1},message:"must be >= -1"}],!1}return N.errors=t,r===0}N.evaluated={dynamicProps:!1,dynamicItems:!1};function k(e,{instancePath:n="",parentData:w,parentDataProperty:b,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=k.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(Array.isArray(e)){if(e.length>168)return k.errors=[{instancePath:n,schemaPath:"#/maxItems",keyword:"maxItems",params:{limit:168},message:"must NOT have more than 168 items"}],!1;if(e.length<168)return k.errors=[{instancePath:n,schemaPath:"#/minItems",keyword:"minItems",params:{limit:168},message:"must NOT have fewer than 168 items"}],!1;{var p=!0;let y=e.length;for(let a=0;a511)return m.errors=[{instancePath:n+"/url",schemaPath:"#/properties/url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return m.errors=[{instancePath:n+"/url",schemaPath:"#/properties/url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=l===r}else var i=!0;if(i){if(e.enabled!==void 0){let s=r;if(typeof e.enabled!="boolean")return m.errors=[{instancePath:n+"/enabled",schemaPath:"#/properties/enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.streaming_enabled!==void 0){let s=r;if(typeof e.streaming_enabled!="boolean")return m.errors=[{instancePath:n+"/streaming_enabled",schemaPath:"#/properties/streaming_enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.playback_transport!==void 0){let s=r;F(e.playback_transport,{instancePath:n+"/playback_transport",parentData:e,parentDataProperty:"playback_transport",rootData:c,dynamicAnchors:d})||(t=t===null?F.errors:t.concat(F.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.eptz_config!==void 0){let s=r;L(e.eptz_config,{instancePath:n+"/eptz_config",parentData:e,parentDataProperty:"eptz_config",rootData:c,dynamicAnchors:d})||(t=t===null?L.errors:t.concat(L.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.width!==void 0){let s=r;f(e.width,{instancePath:n+"/width",parentData:e,parentDataProperty:"width",rootData:c,dynamicAnchors:d})||(t=t===null?f.errors:t.concat(f.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.height!==void 0){let s=r;f(e.height,{instancePath:n+"/height",parentData:e,parentDataProperty:"height",rootData:c,dynamicAnchors:d})||(t=t===null?f.errors:t.concat(f.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.fps!==void 0){let s=r;f(e.fps,{instancePath:n+"/fps",parentData:e,parentDataProperty:"fps",rootData:c,dynamicAnchors:d})||(t=t===null?f.errors:t.concat(f.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.codec!==void 0){let s=e.codec,l=r;if(r===l)if(typeof s=="string"){if(v(s)>15)return m.errors=[{instancePath:n+"/codec",schemaPath:"#/properties/codec/maxLength",keyword:"maxLength",params:{limit:15},message:"must NOT have more than 15 characters"}],!1}else return m.errors=[{instancePath:n+"/codec",schemaPath:"#/properties/codec/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=l===r}else var i=!0;if(i){if(e.priority!==void 0){let s=r;f(e.priority,{instancePath:n+"/priority",parentData:e,parentDataProperty:"priority",rootData:c,dynamicAnchors:d})||(t=t===null?f.errors:t.concat(f.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.record!==void 0){let s=r;if(typeof e.record!="boolean")return m.errors=[{instancePath:n+"/record",schemaPath:"#/properties/record/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.segment_duration!==void 0){let s=r;f(e.segment_duration,{instancePath:n+"/segment_duration",parentData:e,parentDataProperty:"segment_duration",rootData:c,dynamicAnchors:d})||(t=t===null?f.errors:t.concat(f.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.detection_based_recording!==void 0){let s=r;if(typeof e.detection_based_recording!="boolean")return m.errors=[{instancePath:n+"/detection_based_recording",schemaPath:"#/properties/detection_based_recording/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.detection_model!==void 0){let s=e.detection_model,l=r;if(r===l)if(typeof s=="string"){if(v(s)>511)return m.errors=[{instancePath:n+"/detection_model",schemaPath:"#/properties/detection_model/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return m.errors=[{instancePath:n+"/detection_model",schemaPath:"#/properties/detection_model/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=l===r}else var i=!0;if(i){if(e.detection_threshold!==void 0){let s=e.detection_threshold,l=r;if(!(typeof s=="number"&&!(s%1)&&!isNaN(s)&&isFinite(s)))return m.errors=[{instancePath:n+"/detection_threshold",schemaPath:"#/properties/detection_threshold/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;var i=l===r}else var i=!0;if(i){if(e.detection_interval!==void 0){let s=r;f(e.detection_interval,{instancePath:n+"/detection_interval",parentData:e,parentDataProperty:"detection_interval",rootData:c,dynamicAnchors:d})||(t=t===null?f.errors:t.concat(f.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.pre_detection_buffer!==void 0){let s=r;f(e.pre_detection_buffer,{instancePath:n+"/pre_detection_buffer",parentData:e,parentDataProperty:"pre_detection_buffer",rootData:c,dynamicAnchors:d})||(t=t===null?f.errors:t.concat(f.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.post_detection_buffer!==void 0){let s=r;f(e.post_detection_buffer,{instancePath:n+"/post_detection_buffer",parentData:e,parentDataProperty:"post_detection_buffer",rootData:c,dynamicAnchors:d})||(t=t===null?f.errors:t.concat(f.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.detection_object_filter!==void 0){let s=e.detection_object_filter,l=r;if(r===l)if(typeof s=="string"){if(v(s)>15)return m.errors=[{instancePath:n+"/detection_object_filter",schemaPath:"#/properties/detection_object_filter/maxLength",keyword:"maxLength",params:{limit:15},message:"must NOT have more than 15 characters"}],!1}else return m.errors=[{instancePath:n+"/detection_object_filter",schemaPath:"#/properties/detection_object_filter/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=l===r}else var i=!0;if(i){if(e.detection_object_filter_list!==void 0){let s=e.detection_object_filter_list,l=r;if(r===l)if(typeof s=="string"){if(v(s)>255)return m.errors=[{instancePath:n+"/detection_object_filter_list",schemaPath:"#/properties/detection_object_filter_list/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1}else return m.errors=[{instancePath:n+"/detection_object_filter_list",schemaPath:"#/properties/detection_object_filter_list/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=l===r}else var i=!0;if(i){if(e.protocol!==void 0){let s=r;I(e.protocol,{instancePath:n+"/protocol",parentData:e,parentDataProperty:"protocol",rootData:c,dynamicAnchors:d})||(t=t===null?I.errors:t.concat(I.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.record_audio!==void 0){let s=r;if(typeof e.record_audio!="boolean")return m.errors=[{instancePath:n+"/record_audio",schemaPath:"#/properties/record_audio/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.audio_voice_enhancement!==void 0){let s=r;if(typeof e.audio_voice_enhancement!="boolean")return m.errors=[{instancePath:n+"/audio_voice_enhancement",schemaPath:"#/properties/audio_voice_enhancement/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.isOnvif!==void 0){let s=r;if(typeof e.isOnvif!="boolean")return m.errors=[{instancePath:n+"/isOnvif",schemaPath:"#/properties/isOnvif/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.backchannel_enabled!==void 0){let s=r;if(typeof e.backchannel_enabled!="boolean")return m.errors=[{instancePath:n+"/backchannel_enabled",schemaPath:"#/properties/backchannel_enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.retention_days!==void 0){let s=r;N(e.retention_days,{instancePath:n+"/retention_days",parentData:e,parentDataProperty:"retention_days",rootData:c,dynamicAnchors:d})||(t=t===null?N.errors:t.concat(N.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.detection_retention_days!==void 0){let s=r;N(e.detection_retention_days,{instancePath:n+"/detection_retention_days",parentData:e,parentDataProperty:"detection_retention_days",rootData:c,dynamicAnchors:d})||(t=t===null?N.errors:t.concat(N.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.max_storage_mb!==void 0){let s=r;f(e.max_storage_mb,{instancePath:n+"/max_storage_mb",parentData:e,parentDataProperty:"max_storage_mb",rootData:c,dynamicAnchors:d})||(t=t===null?f.errors:t.concat(f.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.tier_critical_multiplier!==void 0){let s=e.tier_critical_multiplier,l=r;if(!(typeof s=="number"&&isFinite(s)))return m.errors=[{instancePath:n+"/tier_critical_multiplier",schemaPath:"#/properties/tier_critical_multiplier/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var i=l===r}else var i=!0;if(i){if(e.tier_important_multiplier!==void 0){let s=e.tier_important_multiplier,l=r;if(!(typeof s=="number"&&isFinite(s)))return m.errors=[{instancePath:n+"/tier_important_multiplier",schemaPath:"#/properties/tier_important_multiplier/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var i=l===r}else var i=!0;if(i){if(e.tier_ephemeral_multiplier!==void 0){let s=e.tier_ephemeral_multiplier,l=r;if(!(typeof s=="number"&&isFinite(s)))return m.errors=[{instancePath:n+"/tier_ephemeral_multiplier",schemaPath:"#/properties/tier_ephemeral_multiplier/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var i=l===r}else var i=!0;if(i){if(e.storage_priority!==void 0){let s=r;f(e.storage_priority,{instancePath:n+"/storage_priority",parentData:e,parentDataProperty:"storage_priority",rootData:c,dynamicAnchors:d})||(t=t===null?f.errors:t.concat(f.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.ptz_enabled!==void 0){let s=r;if(typeof e.ptz_enabled!="boolean")return m.errors=[{instancePath:n+"/ptz_enabled",schemaPath:"#/properties/ptz_enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.ptz_max_x!==void 0){let s=r;f(e.ptz_max_x,{instancePath:n+"/ptz_max_x",parentData:e,parentDataProperty:"ptz_max_x",rootData:c,dynamicAnchors:d})||(t=t===null?f.errors:t.concat(f.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.ptz_max_y!==void 0){let s=r;f(e.ptz_max_y,{instancePath:n+"/ptz_max_y",parentData:e,parentDataProperty:"ptz_max_y",rootData:c,dynamicAnchors:d})||(t=t===null?f.errors:t.concat(f.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.ptz_max_z!==void 0){let s=r;f(e.ptz_max_z,{instancePath:n+"/ptz_max_z",parentData:e,parentDataProperty:"ptz_max_z",rootData:c,dynamicAnchors:d})||(t=t===null?f.errors:t.concat(f.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.ptz_has_home!==void 0){let s=r;if(typeof e.ptz_has_home!="boolean")return m.errors=[{instancePath:n+"/ptz_has_home",schemaPath:"#/properties/ptz_has_home/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.onvif_username!==void 0){let s=e.onvif_username,l=r;if(r===l)if(typeof s=="string"){if(v(s)>63)return m.errors=[{instancePath:n+"/onvif_username",schemaPath:"#/properties/onvif_username/maxLength",keyword:"maxLength",params:{limit:63},message:"must NOT have more than 63 characters"}],!1}else return m.errors=[{instancePath:n+"/onvif_username",schemaPath:"#/properties/onvif_username/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=l===r}else var i=!0;if(i){if(e.onvif_password!==void 0){let s=e.onvif_password,l=r;if(r===l)if(typeof s=="string"){if(v(s)>63)return m.errors=[{instancePath:n+"/onvif_password",schemaPath:"#/properties/onvif_password/maxLength",keyword:"maxLength",params:{limit:63},message:"must NOT have more than 63 characters"}],!1}else return m.errors=[{instancePath:n+"/onvif_password",schemaPath:"#/properties/onvif_password/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=l===r}else var i=!0;if(i){if(e.onvif_profile!==void 0){let s=e.onvif_profile,l=r;if(r===l)if(typeof s=="string"){if(v(s)>63)return m.errors=[{instancePath:n+"/onvif_profile",schemaPath:"#/properties/onvif_profile/maxLength",keyword:"maxLength",params:{limit:63},message:"must NOT have more than 63 characters"}],!1}else return m.errors=[{instancePath:n+"/onvif_profile",schemaPath:"#/properties/onvif_profile/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=l===r}else var i=!0;if(i){if(e.onvif_port!==void 0){let s=r;f(e.onvif_port,{instancePath:n+"/onvif_port",parentData:e,parentDataProperty:"onvif_port",rootData:c,dynamicAnchors:d})||(t=t===null?f.errors:t.concat(f.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.record_on_schedule!==void 0){let s=r;if(typeof e.record_on_schedule!="boolean")return m.errors=[{instancePath:n+"/record_on_schedule",schemaPath:"#/properties/record_on_schedule/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.recording_schedule!==void 0){let s=r;k(e.recording_schedule,{instancePath:n+"/recording_schedule",parentData:e,parentDataProperty:"recording_schedule",rootData:c,dynamicAnchors:d})||(t=t===null?k.errors:t.concat(k.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.detection_record_on_schedule!==void 0){let s=r;if(typeof e.detection_record_on_schedule!="boolean")return m.errors=[{instancePath:n+"/detection_record_on_schedule",schemaPath:"#/properties/detection_record_on_schedule/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.detection_recording_schedule!==void 0){let s=r;k(e.detection_recording_schedule,{instancePath:n+"/detection_recording_schedule",parentData:e,parentDataProperty:"detection_recording_schedule",rootData:c,dynamicAnchors:d})||(t=t===null?k.errors:t.concat(k.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.tags!==void 0){let s=e.tags,l=r;if(r===l)if(typeof s=="string"){if(v(s)>255)return m.errors=[{instancePath:n+"/tags",schemaPath:"#/properties/tags/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1}else return m.errors=[{instancePath:n+"/tags",schemaPath:"#/properties/tags/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=l===r}else var i=!0;if(i){if(e.admin_url!==void 0){let s=e.admin_url,l=r;if(r===l)if(typeof s=="string"){if(v(s)>511)return m.errors=[{instancePath:n+"/admin_url",schemaPath:"#/properties/admin_url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return m.errors=[{instancePath:n+"/admin_url",schemaPath:"#/properties/admin_url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=l===r}else var i=!0;if(i){if(e.privacy_mode!==void 0){let s=r;if(typeof e.privacy_mode!="boolean")return m.errors=[{instancePath:n+"/privacy_mode",schemaPath:"#/properties/privacy_mode/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.can_control_privacy!==void 0){let s=r;if(typeof e.can_control_privacy!="boolean")return m.errors=[{instancePath:n+"/can_control_privacy",schemaPath:"#/properties/can_control_privacy/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.motion_trigger_source!==void 0){let s=e.motion_trigger_source,l=r;if(r===l)if(typeof s=="string"){if(v(s)>255)return m.errors=[{instancePath:n+"/motion_trigger_source",schemaPath:"#/properties/motion_trigger_source/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1}else return m.errors=[{instancePath:n+"/motion_trigger_source",schemaPath:"#/properties/motion_trigger_source/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=l===r}else var i=!0;if(i){if(e.go2rtc_source_override!==void 0){let s=e.go2rtc_source_override,l=r;if(r===l)if(typeof s=="string"){if(v(s)>8191)return m.errors=[{instancePath:n+"/go2rtc_source_override",schemaPath:"#/properties/go2rtc_source_override/maxLength",keyword:"maxLength",params:{limit:8191},message:"must NOT have more than 8191 characters"}],!1}else return m.errors=[{instancePath:n+"/go2rtc_source_override",schemaPath:"#/properties/go2rtc_source_override/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=l===r}else var i=!0;if(i){if(e.sub_stream_url!==void 0){let s=e.sub_stream_url,l=r;if(r===l)if(typeof s=="string"){if(v(s)>511)return m.errors=[{instancePath:n+"/sub_stream_url",schemaPath:"#/properties/sub_stream_url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return m.errors=[{instancePath:n+"/sub_stream_url",schemaPath:"#/properties/sub_stream_url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=l===r}else var i=!0;if(i){if(e.has_sub_stream!==void 0){let s=r;if(typeof e.has_sub_stream!="boolean")return m.errors=[{instancePath:n+"/has_sub_stream",schemaPath:"#/properties/has_sub_stream/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.detection_url!==void 0){let s=e.detection_url,l=r;if(r===l)if(typeof s=="string"){if(v(s)>511)return m.errors=[{instancePath:n+"/detection_url",schemaPath:"#/properties/detection_url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return m.errors=[{instancePath:n+"/detection_url",schemaPath:"#/properties/detection_url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=l===r}else var i=!0;if(i){if(e.publish_url!==void 0){let s=e.publish_url,l=r;if(r===l)if(typeof s=="string"){if(v(s)>511)return m.errors=[{instancePath:n+"/publish_url",schemaPath:"#/properties/publish_url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return m.errors=[{instancePath:n+"/publish_url",schemaPath:"#/properties/publish_url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=l===r}else var i=!0;if(i){if(e.status!==void 0){let s=r;T(e.status,{instancePath:n+"/status",parentData:e,parentDataProperty:"status",rootData:c,dynamicAnchors:d})||(t=t===null?T.errors:t.concat(T.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.error_message!==void 0){let s=e.error_message,l=r;if(r===l)if(typeof s=="string"){if(v(s)>255)return m.errors=[{instancePath:n+"/error_message",schemaPath:"#/properties/error_message/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1;if(v(s)<1)return m.errors=[{instancePath:n+"/error_message",schemaPath:"#/properties/error_message/minLength",keyword:"minLength",params:{limit:1},message:"must NOT have fewer than 1 characters"}],!1}else return m.errors=[{instancePath:n+"/error_message",schemaPath:"#/properties/error_message/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=l===r}else var i=!0;if(i)if(e.go2rtc_hls_available!==void 0){let s=r;if(typeof e.go2rtc_hls_available!="boolean")return m.errors=[{instancePath:n+"/go2rtc_hls_available",schemaPath:"#/properties/go2rtc_hls_available/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}else return m.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return m.errors=t,r===0}m.evaluated={props:{camera_uuid:!0,location_uuid:!0,name:!0,url:!0,enabled:!0,streaming_enabled:!0,playback_transport:!0,eptz_config:!0,width:!0,height:!0,fps:!0,codec:!0,priority:!0,record:!0,segment_duration:!0,detection_based_recording:!0,detection_model:!0,detection_threshold:!0,detection_interval:!0,pre_detection_buffer:!0,post_detection_buffer:!0,detection_object_filter:!0,detection_object_filter_list:!0,protocol:!0,record_audio:!0,audio_voice_enhancement:!0,isOnvif:!0,backchannel_enabled:!0,retention_days:!0,detection_retention_days:!0,max_storage_mb:!0,tier_critical_multiplier:!0,tier_important_multiplier:!0,tier_ephemeral_multiplier:!0,storage_priority:!0,ptz_enabled:!0,ptz_max_x:!0,ptz_max_y:!0,ptz_max_z:!0,ptz_has_home:!0,onvif_username:!0,onvif_password:!0,onvif_profile:!0,onvif_port:!0,record_on_schedule:!0,recording_schedule:!0,detection_record_on_schedule:!0,detection_recording_schedule:!0,tags:!0,admin_url:!0,privacy_mode:!0,can_control_privacy:!0,motion_trigger_source:!0,go2rtc_source_override:!0,sub_stream_url:!0,has_sub_stream:!0,detection_url:!0,publish_url:!0,status:!0,error_message:!0,go2rtc_hls_available:!0},dynamicProps:!1,dynamicItems:!1};var B=new RegExp("^(?![A-Za-z][A-Za-z0-9+.-]*://[^/?#]*@)","u");function O(e,{instancePath:n="",parentData:w,parentDataProperty:b,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=O.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(typeof e=="string"){if(v(e)>511)return O.errors=[{instancePath:n,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1;if(!B.test(e))return O.errors=[{instancePath:n,schemaPath:"#/pattern",keyword:"pattern",params:{pattern:"^(?![A-Za-z][A-Za-z0-9+.-]*://[^/?#]*@)"},message:'must match pattern "^(?![A-Za-z][A-Za-z0-9+.-]*://[^/?#]*@)"'}],!1}else return O.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return O.errors=t,r===0}O.evaluated={dynamicProps:!1,dynamicItems:!1};function j(e,{instancePath:n="",parentData:w,parentDataProperty:b,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=j.evaluated;o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0);let p=r;m(e,{instancePath:n,parentData:w,parentDataProperty:b,rootData:c,dynamicAnchors:d})||(t=t===null?m.errors:t.concat(m.errors),r=t.length);var y=p===r;if(y){let q=r,i=r,u=!0,s=r,l=r,P=!1,Y=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let g;if(e.isOnvif===void 0&&(g="isOnvif")){let x={};t===null?t=[x]:t.push(x),r++}else if(e.isOnvif!==void 0&&e.isOnvif!==!0){let x={};t===null?t=[x]:t.push(x),r++}}var _=Y===r;if(P=P||_,_){var a={};a.isOnvif=!0}let S=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let g;if(e.onvif_username===void 0&&(g="onvif_username")){let x={};t===null?t=[x]:t.push(x),r++}else if(e.onvif_username!==void 0){let x=e.onvif_username;if(typeof x=="string"&&v(x)<1){let A={};t===null?t=[A]:t.push(A),r++}}}var _=S===r;P=P||_,_&&a!==!0&&(a=a||{},a.onvif_username=!0);let C=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let g;if(e.onvif_password===void 0&&(g="onvif_password")){let x={};t===null?t=[x]:t.push(x),r++}else if(e.onvif_password!==void 0){let x=e.onvif_password;if(typeof x=="string"&&v(x)<1){let A={};t===null?t=[A]:t.push(A),r++}}}var _=C===r;if(P=P||_,_&&a!==!0&&(a=a||{},a.onvif_password=!0),P)r=l,t!==null&&(l?t.length=l:t=null);else{let g={};t===null?t=[g]:t.push(g),r++}var $=s===r;if(r=i,t!==null&&(i?t.length=i:t=null),$){let g=r;e&&typeof e=="object"&&!Array.isArray(e)&&e.url!==void 0&&(O(e.url,{instancePath:n+"/url",parentData:e,parentDataProperty:"url",rootData:c,dynamicAnchors:d})||(t=t===null?O.errors:t.concat(O.errors),r=t.length));var $=g===r;u=$,u&&a!==!0&&(a=a||{},a.url=!0)}if(!u){let g={instancePath:n,schemaPath:"#/allOf/1/if",keyword:"if",params:{failingKeyword:"then"},message:'must match "then" schema'};return t===null?t=[g]:t.push(g),r++,j.errors=t,!1}if(r===q)if(e&&typeof e=="object"&&!Array.isArray(e)){let g;if(e.url===void 0&&(g="url")||e.onvif_username===void 0&&(g="onvif_username")||e.onvif_password===void 0&&(g="onvif_password")||e.admin_url===void 0&&(g="admin_url")||e.go2rtc_source_override===void 0&&(g="go2rtc_source_override")||e.sub_stream_url===void 0&&(g="sub_stream_url")||e.detection_url===void 0&&(g="detection_url")||e.publish_url===void 0&&(g="publish_url"))return j.errors=[{instancePath:n,schemaPath:"#/allOf/1/required",keyword:"required",params:{missingProperty:g},message:"must have required property '"+g+"'"}],!1;if(a!==!0&&(a=a||{},a.can_control_privacy=!0),e.can_control_privacy!==void 0&&e.can_control_privacy!==!0)return j.errors=[{instancePath:n+"/can_control_privacy",schemaPath:"#/allOf/1/properties/can_control_privacy/const",keyword:"const",params:{allowedValue:!0},message:"must be equal to constant"}],!1}else return j.errors=[{instancePath:n,schemaPath:"#/allOf/1/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;var y=q===r;y&&a!==!0&&(a=a||{},a.camera_uuid=!0,a.location_uuid=!0,a.name=!0,a.url=!0,a.enabled=!0,a.streaming_enabled=!0,a.playback_transport=!0,a.eptz_config=!0,a.width=!0,a.height=!0,a.fps=!0,a.codec=!0,a.priority=!0,a.record=!0,a.segment_duration=!0,a.detection_based_recording=!0,a.detection_model=!0,a.detection_threshold=!0,a.detection_interval=!0,a.pre_detection_buffer=!0,a.post_detection_buffer=!0,a.detection_object_filter=!0,a.detection_object_filter_list=!0,a.protocol=!0,a.record_audio=!0,a.audio_voice_enhancement=!0,a.isOnvif=!0,a.backchannel_enabled=!0,a.retention_days=!0,a.detection_retention_days=!0,a.max_storage_mb=!0,a.tier_critical_multiplier=!0,a.tier_important_multiplier=!0,a.tier_ephemeral_multiplier=!0,a.storage_priority=!0,a.ptz_enabled=!0,a.ptz_max_x=!0,a.ptz_max_y=!0,a.ptz_max_z=!0,a.ptz_has_home=!0,a.onvif_username=!0,a.onvif_password=!0,a.onvif_profile=!0,a.onvif_port=!0,a.record_on_schedule=!0,a.recording_schedule=!0,a.detection_record_on_schedule=!0,a.detection_recording_schedule=!0,a.tags=!0,a.admin_url=!0,a.privacy_mode=!0,a.can_control_privacy=!0,a.motion_trigger_source=!0,a.go2rtc_source_override=!0,a.sub_stream_url=!0,a.has_sub_stream=!0,a.detection_url=!0,a.publish_url=!0,a.status=!0,a.error_message=!0,a.go2rtc_hls_available=!0)}return j.errors=t,o.props=a,r===0}j.evaluated={dynamicProps:!0,dynamicItems:!1};export{M as validateStreamDetailConfigurable}; diff --git a/web/js/api/generated/validators/StreamDetailLiveOnly.d.mts b/web/js/api/generated/validators/StreamDetailLiveOnly.d.mts new file mode 100644 index 000000000..023e77416 --- /dev/null +++ b/web/js/api/generated/validators/StreamDetailLiveOnly.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateStreamDetailLiveOnly } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/StreamDetailLiveOnly.mjs b/web/js/api/generated/validators/StreamDetailLiveOnly.mjs new file mode 100644 index 000000000..8acc2dccc --- /dev/null +++ b/web/js/api/generated/validators/StreamDetailLiveOnly.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var X=(e,s)=>()=>{try{return s||e((s={exports:{}}).exports,s),s.exports}catch(b){throw s=0,b}};var q=X(Y=>{"use strict";Object.defineProperty(Y,"__esModule",{value:!0});function V(e){let s=e.length,b=0,v=0,p;for(;v=55296&&p<=56319&&v36)return x.errors=[{instancePath:s,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:36},message:"must NOT have more than 36 characters"}],!1;if(!R.test(e))return x.errors=[{instancePath:s,schemaPath:"#/pattern",keyword:"pattern",params:{pattern:"^(|[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})$"},message:'must match pattern "^(|[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})$"'}],!1}else return x.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return x.errors=t,r===0}x.evaluated={dynamicProps:!1,dynamicItems:!1};function D(e,{instancePath:s="",parentData:b,parentDataProperty:v,rootData:p=e,dynamicAnchors:f={}}={}){let t=null,r=0,o=D.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(typeof e=="string"){if(w(e)>255)return D.errors=[{instancePath:s,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1;if(w(e)<1)return D.errors=[{instancePath:s,schemaPath:"#/minLength",keyword:"minLength",params:{limit:1},message:"must NOT have fewer than 1 characters"}],!1;if(new TextEncoder().encode(e).byteLength>255)return D.errors=[{instancePath:s,schemaPath:"#/x-lightnvr-max-bytes",keyword:"x-lightnvr-max-bytes",params:{},message:"must fit the UTF-8 byte limit"}],!1}else return D.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return D.errors=t,r===0}D.evaluated={dynamicProps:!1,dynamicItems:!1};var C={type:"string",enum:["auto","webrtc_only","mse_only","hls_only","webrtc_then_mse","mse_then_hls"],$id:"urn:lightnvr:contract:PlaybackTransport"};function L(e,{instancePath:s="",parentData:b,parentDataProperty:v,rootData:p=e,dynamicAnchors:f={}}={}){let t=null,r=0,o=L.evaluated;return o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e!="string"?(L.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1):e==="auto"||e==="webrtc_only"||e==="mse_only"||e==="hls_only"||e==="webrtc_then_mse"||e==="mse_then_hls"?(L.errors=t,r===0):(L.errors=[{instancePath:s,schemaPath:"#/enum",keyword:"enum",params:{allowedValues:C.enum},message:"must be equal to one of the allowed values"}],!1)}L.evaluated={dynamicProps:!1,dynamicItems:!1};function h(e,{instancePath:s="",parentData:b,parentDataProperty:v,rootData:p=e,dynamicAnchors:f={}}={}){let t=null,r=0,o=h.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(e&&typeof e=="object"&&!Array.isArray(e)){let y;if(e.version===void 0&&(y="version")||e.projection===void 0&&(y="projection")||e.mount===void 0&&(y="mount")||e.centerX===void 0&&(y="centerX")||e.centerY===void 0&&(y="centerY")||e.radius===void 0&&(y="radius")||e.fov===void 0&&(y="fov")||e.rotation===void 0&&(y="rotation")||e.defaultYaw===void 0&&(y="defaultYaw")||e.defaultTilt===void 0&&(y="defaultTilt")||e.defaultViewFov===void 0&&(y="defaultViewFov"))return h.errors=[{instancePath:s,schemaPath:"#/required",keyword:"required",params:{missingProperty:y},message:"must have required property '"+y+"'"}],!1;if(e.version!==void 0){let a=r;if(e.version!==1)return h.errors=[{instancePath:s+"/version",schemaPath:"#/properties/version/const",keyword:"const",params:{allowedValue:1},message:"must be equal to constant"}],!1;var d=a===r}else var d=!0;if(d){if(e.projection!==void 0){let a=r;if(e.projection!=="equidistant")return h.errors=[{instancePath:s+"/projection",schemaPath:"#/properties/projection/const",keyword:"const",params:{allowedValue:"equidistant"},message:"must be equal to constant"}],!1;var d=a===r}else var d=!0;if(d){if(e.mount!==void 0){let a=r;if(e.mount!=="ceiling")return h.errors=[{instancePath:s+"/mount",schemaPath:"#/properties/mount/const",keyword:"const",params:{allowedValue:"ceiling"},message:"must be equal to constant"}],!1;var d=a===r}else var d=!0;if(d){if(e.centerX!==void 0){let a=e.centerX,c=r;if(r===c)if(typeof a=="number"&&isFinite(a)){if(a>1||isNaN(a))return h.errors=[{instancePath:s+"/centerX",schemaPath:"#/properties/centerX/maximum",keyword:"maximum",params:{comparison:"<=",limit:1},message:"must be <= 1"}],!1;if(a<0||isNaN(a))return h.errors=[{instancePath:s+"/centerX",schemaPath:"#/properties/centerX/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1}else return h.errors=[{instancePath:s+"/centerX",schemaPath:"#/properties/centerX/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var d=c===r}else var d=!0;if(d){if(e.centerY!==void 0){let a=e.centerY,c=r;if(r===c)if(typeof a=="number"&&isFinite(a)){if(a>1||isNaN(a))return h.errors=[{instancePath:s+"/centerY",schemaPath:"#/properties/centerY/maximum",keyword:"maximum",params:{comparison:"<=",limit:1},message:"must be <= 1"}],!1;if(a<0||isNaN(a))return h.errors=[{instancePath:s+"/centerY",schemaPath:"#/properties/centerY/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1}else return h.errors=[{instancePath:s+"/centerY",schemaPath:"#/properties/centerY/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var d=c===r}else var d=!0;if(d){if(e.radius!==void 0){let a=e.radius,c=r;if(r===c)if(typeof a=="number"&&isFinite(a)){if(a>1||isNaN(a))return h.errors=[{instancePath:s+"/radius",schemaPath:"#/properties/radius/maximum",keyword:"maximum",params:{comparison:"<=",limit:1},message:"must be <= 1"}],!1;if(a<.05||isNaN(a))return h.errors=[{instancePath:s+"/radius",schemaPath:"#/properties/radius/minimum",keyword:"minimum",params:{comparison:">=",limit:.05},message:"must be >= 0.05"}],!1}else return h.errors=[{instancePath:s+"/radius",schemaPath:"#/properties/radius/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var d=c===r}else var d=!0;if(d){if(e.fov!==void 0){let a=e.fov,c=r;if(r===c)if(typeof a=="number"&&isFinite(a)){if(a>360||isNaN(a))return h.errors=[{instancePath:s+"/fov",schemaPath:"#/properties/fov/maximum",keyword:"maximum",params:{comparison:"<=",limit:360},message:"must be <= 360"}],!1;if(a<160||isNaN(a))return h.errors=[{instancePath:s+"/fov",schemaPath:"#/properties/fov/minimum",keyword:"minimum",params:{comparison:">=",limit:160},message:"must be >= 160"}],!1}else return h.errors=[{instancePath:s+"/fov",schemaPath:"#/properties/fov/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var d=c===r}else var d=!0;if(d){if(e.rotation!==void 0){let a=e.rotation,c=r;if(r===c)if(typeof a=="number"&&isFinite(a)){if(a>360||isNaN(a))return h.errors=[{instancePath:s+"/rotation",schemaPath:"#/properties/rotation/maximum",keyword:"maximum",params:{comparison:"<=",limit:360},message:"must be <= 360"}],!1;if(a<-360||isNaN(a))return h.errors=[{instancePath:s+"/rotation",schemaPath:"#/properties/rotation/minimum",keyword:"minimum",params:{comparison:">=",limit:-360},message:"must be >= -360"}],!1}else return h.errors=[{instancePath:s+"/rotation",schemaPath:"#/properties/rotation/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var d=c===r}else var d=!0;if(d){if(e.defaultYaw!==void 0){let a=e.defaultYaw,c=r;if(r===c)if(typeof a=="number"&&isFinite(a)){if(a>360||isNaN(a))return h.errors=[{instancePath:s+"/defaultYaw",schemaPath:"#/properties/defaultYaw/maximum",keyword:"maximum",params:{comparison:"<=",limit:360},message:"must be <= 360"}],!1;if(a<-360||isNaN(a))return h.errors=[{instancePath:s+"/defaultYaw",schemaPath:"#/properties/defaultYaw/minimum",keyword:"minimum",params:{comparison:">=",limit:-360},message:"must be >= -360"}],!1}else return h.errors=[{instancePath:s+"/defaultYaw",schemaPath:"#/properties/defaultYaw/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var d=c===r}else var d=!0;if(d){if(e.defaultTilt!==void 0){let a=e.defaultTilt,c=r;if(r===c)if(typeof a=="number"&&isFinite(a)){if(a>30||isNaN(a))return h.errors=[{instancePath:s+"/defaultTilt",schemaPath:"#/properties/defaultTilt/maximum",keyword:"maximum",params:{comparison:"<=",limit:30},message:"must be <= 30"}],!1;if(a<-90||isNaN(a))return h.errors=[{instancePath:s+"/defaultTilt",schemaPath:"#/properties/defaultTilt/minimum",keyword:"minimum",params:{comparison:">=",limit:-90},message:"must be >= -90"}],!1}else return h.errors=[{instancePath:s+"/defaultTilt",schemaPath:"#/properties/defaultTilt/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var d=c===r}else var d=!0;if(d)if(e.defaultViewFov!==void 0){let a=e.defaultViewFov,c=r;if(r===c)if(typeof a=="number"&&isFinite(a)){if(a>120||isNaN(a))return h.errors=[{instancePath:s+"/defaultViewFov",schemaPath:"#/properties/defaultViewFov/maximum",keyword:"maximum",params:{comparison:"<=",limit:120},message:"must be <= 120"}],!1;if(a<20||isNaN(a))return h.errors=[{instancePath:s+"/defaultViewFov",schemaPath:"#/properties/defaultViewFov/minimum",keyword:"minimum",params:{comparison:">=",limit:20},message:"must be >= 20"}],!1}else return h.errors=[{instancePath:s+"/defaultViewFov",schemaPath:"#/properties/defaultViewFov/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var d=c===r}else var d=!0}}}}}}}}}}else return h.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return h.errors=t,r===0}h.evaluated={props:{version:!0,projection:!0,mount:!0,centerX:!0,centerY:!0,radius:!0,fov:!0,rotation:!0,defaultYaw:!0,defaultTilt:!0,defaultViewFov:!0},dynamicProps:!1,dynamicItems:!1};function z(e,{instancePath:s="",parentData:b,parentDataProperty:v,rootData:p=e,dynamicAnchors:f={}}={}){let t=null,r=0,o=z.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(typeof e=="string"){if(w(e)>1023)return z.errors=[{instancePath:s,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:1023},message:"must NOT have more than 1023 characters"}],!1;if(new TextEncoder().encode(e).byteLength>1023)return z.errors=[{instancePath:s,schemaPath:"#/x-lightnvr-max-bytes",keyword:"x-lightnvr-max-bytes",params:{},message:"must fit the UTF-8 byte limit"}],!1;if(e!==""){let y,a=!0;try{y=JSON.parse(e)}catch{a=!1}if(a){let c=r;h(y,{instancePath:s,parentData:e,parentDataProperty:v,rootData:p,dynamicAnchors:f})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var d=c===r;a=d}if(!a)return z.errors=[{instancePath:s,schemaPath:"#/contentSchema",keyword:"contentSchema",params:{},message:"must contain valid JSON matching contentSchema (or be empty)"}],!1}}else return z.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return z.errors=t,r===0}z.evaluated={dynamicProps:!1,dynamicItems:!1};function _(e,{instancePath:s="",parentData:b,parentDataProperty:v,rootData:p=e,dynamicAnchors:f={}}={}){let t=null,r=0,o=_.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),!(typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)))return _.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;if(r===0&&typeof e=="number"&&isFinite(e)){if(e>2147483647||isNaN(e))return _.errors=[{instancePath:s,schemaPath:"#/maximum",keyword:"maximum",params:{comparison:"<=",limit:2147483647},message:"must be <= 2147483647"}],!1;if(e<-2147483648||isNaN(e))return _.errors=[{instancePath:s,schemaPath:"#/minimum",keyword:"minimum",params:{comparison:">=",limit:-2147483648},message:"must be >= -2147483648"}],!1}return _.errors=t,r===0}_.evaluated={dynamicProps:!1,dynamicItems:!1};var W={type:"integer",enum:[0,1],$id:"urn:lightnvr:contract:StreamProtocol"};function $(e,{instancePath:s="",parentData:b,parentDataProperty:v,rootData:p=e,dynamicAnchors:f={}}={}){let t=null,r=0,o=$.evaluated;return o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)?e===0||e===1?($.errors=t,r===0):($.errors=[{instancePath:s,schemaPath:"#/enum",keyword:"enum",params:{allowedValues:W.enum},message:"must be equal to one of the allowed values"}],!1):($.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1)}$.evaluated={dynamicProps:!1,dynamicItems:!1};function k(e,{instancePath:s="",parentData:b,parentDataProperty:v,rootData:p=e,dynamicAnchors:f={}}={}){let t=null,r=0,o=k.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),!(typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)))return k.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;if(r===0&&typeof e=="number"&&isFinite(e)){if(e>365||isNaN(e))return k.errors=[{instancePath:s,schemaPath:"#/maximum",keyword:"maximum",params:{comparison:"<=",limit:365},message:"must be <= 365"}],!1;if(e<-1||isNaN(e))return k.errors=[{instancePath:s,schemaPath:"#/minimum",keyword:"minimum",params:{comparison:">=",limit:-1},message:"must be >= -1"}],!1}return k.errors=t,r===0}k.evaluated={dynamicProps:!1,dynamicItems:!1};function P(e,{instancePath:s="",parentData:b,parentDataProperty:v,rootData:p=e,dynamicAnchors:f={}}={}){let t=null,r=0,o=P.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(Array.isArray(e)){if(e.length>168)return P.errors=[{instancePath:s,schemaPath:"#/maxItems",keyword:"maxItems",params:{limit:168},message:"must NOT have more than 168 items"}],!1;if(e.length<168)return P.errors=[{instancePath:s,schemaPath:"#/minItems",keyword:"minItems",params:{limit:168},message:"must NOT have fewer than 168 items"}],!1;{var d=!0;let y=e.length;for(let a=0;a511)return l.errors=[{instancePath:s+"/url",schemaPath:"#/properties/url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return l.errors=[{instancePath:s+"/url",schemaPath:"#/properties/url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=u===r}else var n=!0;if(n){if(e.enabled!==void 0){let i=r;if(typeof e.enabled!="boolean")return l.errors=[{instancePath:s+"/enabled",schemaPath:"#/properties/enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=i===r}else var n=!0;if(n){if(e.streaming_enabled!==void 0){let i=r;if(typeof e.streaming_enabled!="boolean")return l.errors=[{instancePath:s+"/streaming_enabled",schemaPath:"#/properties/streaming_enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=i===r}else var n=!0;if(n){if(e.playback_transport!==void 0){let i=r;L(e.playback_transport,{instancePath:s+"/playback_transport",parentData:e,parentDataProperty:"playback_transport",rootData:p,dynamicAnchors:f})||(t=t===null?L.errors:t.concat(L.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.eptz_config!==void 0){let i=r;z(e.eptz_config,{instancePath:s+"/eptz_config",parentData:e,parentDataProperty:"eptz_config",rootData:p,dynamicAnchors:f})||(t=t===null?z.errors:t.concat(z.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.width!==void 0){let i=r;_(e.width,{instancePath:s+"/width",parentData:e,parentDataProperty:"width",rootData:p,dynamicAnchors:f})||(t=t===null?_.errors:t.concat(_.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.height!==void 0){let i=r;_(e.height,{instancePath:s+"/height",parentData:e,parentDataProperty:"height",rootData:p,dynamicAnchors:f})||(t=t===null?_.errors:t.concat(_.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.fps!==void 0){let i=r;_(e.fps,{instancePath:s+"/fps",parentData:e,parentDataProperty:"fps",rootData:p,dynamicAnchors:f})||(t=t===null?_.errors:t.concat(_.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.codec!==void 0){let i=e.codec,u=r;if(r===u)if(typeof i=="string"){if(w(i)>15)return l.errors=[{instancePath:s+"/codec",schemaPath:"#/properties/codec/maxLength",keyword:"maxLength",params:{limit:15},message:"must NOT have more than 15 characters"}],!1}else return l.errors=[{instancePath:s+"/codec",schemaPath:"#/properties/codec/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=u===r}else var n=!0;if(n){if(e.priority!==void 0){let i=r;_(e.priority,{instancePath:s+"/priority",parentData:e,parentDataProperty:"priority",rootData:p,dynamicAnchors:f})||(t=t===null?_.errors:t.concat(_.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.record!==void 0){let i=r;if(typeof e.record!="boolean")return l.errors=[{instancePath:s+"/record",schemaPath:"#/properties/record/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=i===r}else var n=!0;if(n){if(e.segment_duration!==void 0){let i=r;_(e.segment_duration,{instancePath:s+"/segment_duration",parentData:e,parentDataProperty:"segment_duration",rootData:p,dynamicAnchors:f})||(t=t===null?_.errors:t.concat(_.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.detection_based_recording!==void 0){let i=r;if(typeof e.detection_based_recording!="boolean")return l.errors=[{instancePath:s+"/detection_based_recording",schemaPath:"#/properties/detection_based_recording/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=i===r}else var n=!0;if(n){if(e.detection_model!==void 0){let i=e.detection_model,u=r;if(r===u)if(typeof i=="string"){if(w(i)>511)return l.errors=[{instancePath:s+"/detection_model",schemaPath:"#/properties/detection_model/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return l.errors=[{instancePath:s+"/detection_model",schemaPath:"#/properties/detection_model/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=u===r}else var n=!0;if(n){if(e.detection_threshold!==void 0){let i=e.detection_threshold,u=r;if(!(typeof i=="number"&&!(i%1)&&!isNaN(i)&&isFinite(i)))return l.errors=[{instancePath:s+"/detection_threshold",schemaPath:"#/properties/detection_threshold/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;var n=u===r}else var n=!0;if(n){if(e.detection_interval!==void 0){let i=r;_(e.detection_interval,{instancePath:s+"/detection_interval",parentData:e,parentDataProperty:"detection_interval",rootData:p,dynamicAnchors:f})||(t=t===null?_.errors:t.concat(_.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.pre_detection_buffer!==void 0){let i=r;_(e.pre_detection_buffer,{instancePath:s+"/pre_detection_buffer",parentData:e,parentDataProperty:"pre_detection_buffer",rootData:p,dynamicAnchors:f})||(t=t===null?_.errors:t.concat(_.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.post_detection_buffer!==void 0){let i=r;_(e.post_detection_buffer,{instancePath:s+"/post_detection_buffer",parentData:e,parentDataProperty:"post_detection_buffer",rootData:p,dynamicAnchors:f})||(t=t===null?_.errors:t.concat(_.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.detection_object_filter!==void 0){let i=e.detection_object_filter,u=r;if(r===u)if(typeof i=="string"){if(w(i)>15)return l.errors=[{instancePath:s+"/detection_object_filter",schemaPath:"#/properties/detection_object_filter/maxLength",keyword:"maxLength",params:{limit:15},message:"must NOT have more than 15 characters"}],!1}else return l.errors=[{instancePath:s+"/detection_object_filter",schemaPath:"#/properties/detection_object_filter/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=u===r}else var n=!0;if(n){if(e.detection_object_filter_list!==void 0){let i=e.detection_object_filter_list,u=r;if(r===u)if(typeof i=="string"){if(w(i)>255)return l.errors=[{instancePath:s+"/detection_object_filter_list",schemaPath:"#/properties/detection_object_filter_list/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1}else return l.errors=[{instancePath:s+"/detection_object_filter_list",schemaPath:"#/properties/detection_object_filter_list/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=u===r}else var n=!0;if(n){if(e.protocol!==void 0){let i=r;$(e.protocol,{instancePath:s+"/protocol",parentData:e,parentDataProperty:"protocol",rootData:p,dynamicAnchors:f})||(t=t===null?$.errors:t.concat($.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.record_audio!==void 0){let i=r;if(typeof e.record_audio!="boolean")return l.errors=[{instancePath:s+"/record_audio",schemaPath:"#/properties/record_audio/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=i===r}else var n=!0;if(n){if(e.audio_voice_enhancement!==void 0){let i=r;if(typeof e.audio_voice_enhancement!="boolean")return l.errors=[{instancePath:s+"/audio_voice_enhancement",schemaPath:"#/properties/audio_voice_enhancement/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=i===r}else var n=!0;if(n){if(e.isOnvif!==void 0){let i=r;if(typeof e.isOnvif!="boolean")return l.errors=[{instancePath:s+"/isOnvif",schemaPath:"#/properties/isOnvif/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=i===r}else var n=!0;if(n){if(e.backchannel_enabled!==void 0){let i=r;if(typeof e.backchannel_enabled!="boolean")return l.errors=[{instancePath:s+"/backchannel_enabled",schemaPath:"#/properties/backchannel_enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=i===r}else var n=!0;if(n){if(e.retention_days!==void 0){let i=r;k(e.retention_days,{instancePath:s+"/retention_days",parentData:e,parentDataProperty:"retention_days",rootData:p,dynamicAnchors:f})||(t=t===null?k.errors:t.concat(k.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.detection_retention_days!==void 0){let i=r;k(e.detection_retention_days,{instancePath:s+"/detection_retention_days",parentData:e,parentDataProperty:"detection_retention_days",rootData:p,dynamicAnchors:f})||(t=t===null?k.errors:t.concat(k.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.max_storage_mb!==void 0){let i=r;_(e.max_storage_mb,{instancePath:s+"/max_storage_mb",parentData:e,parentDataProperty:"max_storage_mb",rootData:p,dynamicAnchors:f})||(t=t===null?_.errors:t.concat(_.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.tier_critical_multiplier!==void 0){let i=e.tier_critical_multiplier,u=r;if(!(typeof i=="number"&&isFinite(i)))return l.errors=[{instancePath:s+"/tier_critical_multiplier",schemaPath:"#/properties/tier_critical_multiplier/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var n=u===r}else var n=!0;if(n){if(e.tier_important_multiplier!==void 0){let i=e.tier_important_multiplier,u=r;if(!(typeof i=="number"&&isFinite(i)))return l.errors=[{instancePath:s+"/tier_important_multiplier",schemaPath:"#/properties/tier_important_multiplier/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var n=u===r}else var n=!0;if(n){if(e.tier_ephemeral_multiplier!==void 0){let i=e.tier_ephemeral_multiplier,u=r;if(!(typeof i=="number"&&isFinite(i)))return l.errors=[{instancePath:s+"/tier_ephemeral_multiplier",schemaPath:"#/properties/tier_ephemeral_multiplier/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var n=u===r}else var n=!0;if(n){if(e.storage_priority!==void 0){let i=r;_(e.storage_priority,{instancePath:s+"/storage_priority",parentData:e,parentDataProperty:"storage_priority",rootData:p,dynamicAnchors:f})||(t=t===null?_.errors:t.concat(_.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.ptz_enabled!==void 0){let i=r;if(typeof e.ptz_enabled!="boolean")return l.errors=[{instancePath:s+"/ptz_enabled",schemaPath:"#/properties/ptz_enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=i===r}else var n=!0;if(n){if(e.ptz_max_x!==void 0){let i=r;_(e.ptz_max_x,{instancePath:s+"/ptz_max_x",parentData:e,parentDataProperty:"ptz_max_x",rootData:p,dynamicAnchors:f})||(t=t===null?_.errors:t.concat(_.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.ptz_max_y!==void 0){let i=r;_(e.ptz_max_y,{instancePath:s+"/ptz_max_y",parentData:e,parentDataProperty:"ptz_max_y",rootData:p,dynamicAnchors:f})||(t=t===null?_.errors:t.concat(_.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.ptz_max_z!==void 0){let i=r;_(e.ptz_max_z,{instancePath:s+"/ptz_max_z",parentData:e,parentDataProperty:"ptz_max_z",rootData:p,dynamicAnchors:f})||(t=t===null?_.errors:t.concat(_.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.ptz_has_home!==void 0){let i=r;if(typeof e.ptz_has_home!="boolean")return l.errors=[{instancePath:s+"/ptz_has_home",schemaPath:"#/properties/ptz_has_home/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=i===r}else var n=!0;if(n){if(e.onvif_username!==void 0){let i=e.onvif_username,u=r;if(r===u)if(typeof i=="string"){if(w(i)>63)return l.errors=[{instancePath:s+"/onvif_username",schemaPath:"#/properties/onvif_username/maxLength",keyword:"maxLength",params:{limit:63},message:"must NOT have more than 63 characters"}],!1}else return l.errors=[{instancePath:s+"/onvif_username",schemaPath:"#/properties/onvif_username/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=u===r}else var n=!0;if(n){if(e.onvif_password!==void 0){let i=e.onvif_password,u=r;if(r===u)if(typeof i=="string"){if(w(i)>63)return l.errors=[{instancePath:s+"/onvif_password",schemaPath:"#/properties/onvif_password/maxLength",keyword:"maxLength",params:{limit:63},message:"must NOT have more than 63 characters"}],!1}else return l.errors=[{instancePath:s+"/onvif_password",schemaPath:"#/properties/onvif_password/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=u===r}else var n=!0;if(n){if(e.onvif_profile!==void 0){let i=e.onvif_profile,u=r;if(r===u)if(typeof i=="string"){if(w(i)>63)return l.errors=[{instancePath:s+"/onvif_profile",schemaPath:"#/properties/onvif_profile/maxLength",keyword:"maxLength",params:{limit:63},message:"must NOT have more than 63 characters"}],!1}else return l.errors=[{instancePath:s+"/onvif_profile",schemaPath:"#/properties/onvif_profile/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=u===r}else var n=!0;if(n){if(e.onvif_port!==void 0){let i=r;_(e.onvif_port,{instancePath:s+"/onvif_port",parentData:e,parentDataProperty:"onvif_port",rootData:p,dynamicAnchors:f})||(t=t===null?_.errors:t.concat(_.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.record_on_schedule!==void 0){let i=r;if(typeof e.record_on_schedule!="boolean")return l.errors=[{instancePath:s+"/record_on_schedule",schemaPath:"#/properties/record_on_schedule/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=i===r}else var n=!0;if(n){if(e.recording_schedule!==void 0){let i=r;P(e.recording_schedule,{instancePath:s+"/recording_schedule",parentData:e,parentDataProperty:"recording_schedule",rootData:p,dynamicAnchors:f})||(t=t===null?P.errors:t.concat(P.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.detection_record_on_schedule!==void 0){let i=r;if(typeof e.detection_record_on_schedule!="boolean")return l.errors=[{instancePath:s+"/detection_record_on_schedule",schemaPath:"#/properties/detection_record_on_schedule/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=i===r}else var n=!0;if(n){if(e.detection_recording_schedule!==void 0){let i=r;P(e.detection_recording_schedule,{instancePath:s+"/detection_recording_schedule",parentData:e,parentDataProperty:"detection_recording_schedule",rootData:p,dynamicAnchors:f})||(t=t===null?P.errors:t.concat(P.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.tags!==void 0){let i=e.tags,u=r;if(r===u)if(typeof i=="string"){if(w(i)>255)return l.errors=[{instancePath:s+"/tags",schemaPath:"#/properties/tags/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1}else return l.errors=[{instancePath:s+"/tags",schemaPath:"#/properties/tags/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=u===r}else var n=!0;if(n){if(e.admin_url!==void 0){let i=e.admin_url,u=r;if(r===u)if(typeof i=="string"){if(w(i)>511)return l.errors=[{instancePath:s+"/admin_url",schemaPath:"#/properties/admin_url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return l.errors=[{instancePath:s+"/admin_url",schemaPath:"#/properties/admin_url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=u===r}else var n=!0;if(n){if(e.privacy_mode!==void 0){let i=r;if(typeof e.privacy_mode!="boolean")return l.errors=[{instancePath:s+"/privacy_mode",schemaPath:"#/properties/privacy_mode/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=i===r}else var n=!0;if(n){if(e.can_control_privacy!==void 0){let i=r;if(typeof e.can_control_privacy!="boolean")return l.errors=[{instancePath:s+"/can_control_privacy",schemaPath:"#/properties/can_control_privacy/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=i===r}else var n=!0;if(n){if(e.motion_trigger_source!==void 0){let i=e.motion_trigger_source,u=r;if(r===u)if(typeof i=="string"){if(w(i)>255)return l.errors=[{instancePath:s+"/motion_trigger_source",schemaPath:"#/properties/motion_trigger_source/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1}else return l.errors=[{instancePath:s+"/motion_trigger_source",schemaPath:"#/properties/motion_trigger_source/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=u===r}else var n=!0;if(n){if(e.go2rtc_source_override!==void 0){let i=e.go2rtc_source_override,u=r;if(r===u)if(typeof i=="string"){if(w(i)>8191)return l.errors=[{instancePath:s+"/go2rtc_source_override",schemaPath:"#/properties/go2rtc_source_override/maxLength",keyword:"maxLength",params:{limit:8191},message:"must NOT have more than 8191 characters"}],!1}else return l.errors=[{instancePath:s+"/go2rtc_source_override",schemaPath:"#/properties/go2rtc_source_override/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=u===r}else var n=!0;if(n){if(e.sub_stream_url!==void 0){let i=e.sub_stream_url,u=r;if(r===u)if(typeof i=="string"){if(w(i)>511)return l.errors=[{instancePath:s+"/sub_stream_url",schemaPath:"#/properties/sub_stream_url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return l.errors=[{instancePath:s+"/sub_stream_url",schemaPath:"#/properties/sub_stream_url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=u===r}else var n=!0;if(n){if(e.has_sub_stream!==void 0){let i=r;if(typeof e.has_sub_stream!="boolean")return l.errors=[{instancePath:s+"/has_sub_stream",schemaPath:"#/properties/has_sub_stream/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=i===r}else var n=!0;if(n){if(e.detection_url!==void 0){let i=e.detection_url,u=r;if(r===u)if(typeof i=="string"){if(w(i)>511)return l.errors=[{instancePath:s+"/detection_url",schemaPath:"#/properties/detection_url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return l.errors=[{instancePath:s+"/detection_url",schemaPath:"#/properties/detection_url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=u===r}else var n=!0;if(n){if(e.publish_url!==void 0){let i=e.publish_url,u=r;if(r===u)if(typeof i=="string"){if(w(i)>511)return l.errors=[{instancePath:s+"/publish_url",schemaPath:"#/properties/publish_url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return l.errors=[{instancePath:s+"/publish_url",schemaPath:"#/properties/publish_url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=u===r}else var n=!0;if(n){if(e.status!==void 0){let i=r;I(e.status,{instancePath:s+"/status",parentData:e,parentDataProperty:"status",rootData:p,dynamicAnchors:f})||(t=t===null?I.errors:t.concat(I.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.error_message!==void 0){let i=e.error_message,u=r;if(r===u)if(typeof i=="string"){if(w(i)>255)return l.errors=[{instancePath:s+"/error_message",schemaPath:"#/properties/error_message/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1;if(w(i)<1)return l.errors=[{instancePath:s+"/error_message",schemaPath:"#/properties/error_message/minLength",keyword:"minLength",params:{limit:1},message:"must NOT have fewer than 1 characters"}],!1}else return l.errors=[{instancePath:s+"/error_message",schemaPath:"#/properties/error_message/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=u===r}else var n=!0;if(n)if(e.go2rtc_hls_available!==void 0){let i=r;if(typeof e.go2rtc_hls_available!="boolean")return l.errors=[{instancePath:s+"/go2rtc_hls_available",schemaPath:"#/properties/go2rtc_hls_available/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=i===r}else var n=!0}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}else return l.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return l.errors=t,r===0}l.evaluated={props:{camera_uuid:!0,location_uuid:!0,name:!0,url:!0,enabled:!0,streaming_enabled:!0,playback_transport:!0,eptz_config:!0,width:!0,height:!0,fps:!0,codec:!0,priority:!0,record:!0,segment_duration:!0,detection_based_recording:!0,detection_model:!0,detection_threshold:!0,detection_interval:!0,pre_detection_buffer:!0,post_detection_buffer:!0,detection_object_filter:!0,detection_object_filter_list:!0,protocol:!0,record_audio:!0,audio_voice_enhancement:!0,isOnvif:!0,backchannel_enabled:!0,retention_days:!0,detection_retention_days:!0,max_storage_mb:!0,tier_critical_multiplier:!0,tier_important_multiplier:!0,tier_ephemeral_multiplier:!0,storage_priority:!0,ptz_enabled:!0,ptz_max_x:!0,ptz_max_y:!0,ptz_max_z:!0,ptz_has_home:!0,onvif_username:!0,onvif_password:!0,onvif_profile:!0,onvif_port:!0,record_on_schedule:!0,recording_schedule:!0,detection_record_on_schedule:!0,detection_recording_schedule:!0,tags:!0,admin_url:!0,privacy_mode:!0,can_control_privacy:!0,motion_trigger_source:!0,go2rtc_source_override:!0,sub_stream_url:!0,has_sub_stream:!0,detection_url:!0,publish_url:!0,status:!0,error_message:!0,go2rtc_hls_available:!0},dynamicProps:!1,dynamicItems:!1};var K=new RegExp("^(?![A-Za-z][A-Za-z0-9+.-]*://[^/?#]*@)","u");function N(e,{instancePath:s="",parentData:b,parentDataProperty:v,rootData:p=e,dynamicAnchors:f={}}={}){let t=null,r=0,o=N.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(typeof e=="string"){if(w(e)>511)return N.errors=[{instancePath:s,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1;if(!K.test(e))return N.errors=[{instancePath:s,schemaPath:"#/pattern",keyword:"pattern",params:{pattern:"^(?![A-Za-z][A-Za-z0-9+.-]*://[^/?#]*@)"},message:'must match pattern "^(?![A-Za-z][A-Za-z0-9+.-]*://[^/?#]*@)"'}],!1}else return N.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return N.errors=t,r===0}N.evaluated={dynamicProps:!1,dynamicItems:!1};var H=new RegExp("[?&;][^=&#]*([Pp][Aa][Ss][Ss]|[Pp][Ww][Dd]|[Tt][Oo][Kk][Ee][Nn]|[Ss][Ee][Cc][Rr][Ee][Tt]|[Aa][Pp][Ii]_?[Kk][Ee][Yy])[^=&#]*=[^&#]","u");function O(e,{instancePath:s="",parentData:b,parentDataProperty:v,rootData:p=e,dynamicAnchors:f={}}={}){let t=null,r=0,o=O.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e!="string")return O.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;let d=r,y=r;if(typeof e=="string"&&!H.test(e)){let c={};t===null?t=[c]:t.push(c),r++}var a=y===r;return a?(O.errors=[{instancePath:s,schemaPath:"#/not",keyword:"not",params:{},message:"must NOT be valid"}],!1):(r=d,t!==null&&(d?t.length=d:t=null),O.errors=t,r===0)}O.evaluated={dynamicProps:!1,dynamicItems:!1};function T(e,{instancePath:s="",parentData:b,parentDataProperty:v,rootData:p=e,dynamicAnchors:f={}}={}){let t=null,r=0,o=T.evaluated;o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0);let d=r;N(e,{instancePath:s,parentData:b,parentDataProperty:v,rootData:p,dynamicAnchors:f})||(t=t===null?N.errors:t.concat(N.errors),r=t.length);var y=d===r;if(y){let a=r;O(e,{instancePath:s,parentData:b,parentDataProperty:v,rootData:p,dynamicAnchors:f})||(t=t===null?O.errors:t.concat(O.errors),r=t.length);var y=a===r}return T.errors=t,r===0}T.evaluated={dynamicProps:!1,dynamicItems:!1};function g(e,{instancePath:s="",parentData:b,parentDataProperty:v,rootData:p=e,dynamicAnchors:f={}}={}){let t=null,r=0,o=g.evaluated;return o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e!="string"?(g.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1):e!==""?(g.errors=[{instancePath:s,schemaPath:"#/const",keyword:"const",params:{allowedValue:""},message:"must be equal to constant"}],!1):(g.errors=t,r===0)}g.evaluated={dynamicProps:!1,dynamicItems:!1};function F(e,{instancePath:s="",parentData:b,parentDataProperty:v,rootData:p=e,dynamicAnchors:f={}}={}){let t=null,r=0,o=F.evaluated;o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0);let d=r;l(e,{instancePath:s,parentData:b,parentDataProperty:v,rootData:p,dynamicAnchors:f})||(t=t===null?l.errors:t.concat(l.errors),r=t.length);var y=d===r;if(y){let j=r,E=r,n=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let i;if(e.detection_api_url===void 0&&(i="detection_api_url")){let u={};t===null?t=[u]:t.push(u),r++}}var a=n===r;if(a)return F.errors=[{instancePath:s,schemaPath:"#/allOf/1/not",keyword:"not",params:{},message:"must NOT be valid"}],!1;if(r=E,t!==null&&(E?t.length=E:t=null),r===j)if(e&&typeof e=="object"&&!Array.isArray(e)){if(e.can_control_privacy!==void 0){let i=r;if(e.can_control_privacy!==!1)return F.errors=[{instancePath:s+"/can_control_privacy",schemaPath:"#/allOf/1/properties/can_control_privacy/const",keyword:"const",params:{allowedValue:!1},message:"must be equal to constant"}],!1;var c=i===r}else var c=!0;if(c){if(e.url!==void 0){let i=r;T(e.url,{instancePath:s+"/url",parentData:e,parentDataProperty:"url",rootData:p,dynamicAnchors:f})||(t=t===null?T.errors:t.concat(T.errors),r=t.length);var c=i===r}else var c=!0;if(c){if(e.onvif_username!==void 0){let i=r;g(e.onvif_username,{instancePath:s+"/onvif_username",parentData:e,parentDataProperty:"onvif_username",rootData:p,dynamicAnchors:f})||(t=t===null?g.errors:t.concat(g.errors),r=t.length);var c=i===r}else var c=!0;if(c){if(e.onvif_password!==void 0){let i=r;g(e.onvif_password,{instancePath:s+"/onvif_password",parentData:e,parentDataProperty:"onvif_password",rootData:p,dynamicAnchors:f})||(t=t===null?g.errors:t.concat(g.errors),r=t.length);var c=i===r}else var c=!0;if(c){if(e.admin_url!==void 0){let i=r;g(e.admin_url,{instancePath:s+"/admin_url",parentData:e,parentDataProperty:"admin_url",rootData:p,dynamicAnchors:f})||(t=t===null?g.errors:t.concat(g.errors),r=t.length);var c=i===r}else var c=!0;if(c){if(e.go2rtc_source_override!==void 0){let i=r;g(e.go2rtc_source_override,{instancePath:s+"/go2rtc_source_override",parentData:e,parentDataProperty:"go2rtc_source_override",rootData:p,dynamicAnchors:f})||(t=t===null?g.errors:t.concat(g.errors),r=t.length);var c=i===r}else var c=!0;if(c){if(e.sub_stream_url!==void 0){let i=r;g(e.sub_stream_url,{instancePath:s+"/sub_stream_url",parentData:e,parentDataProperty:"sub_stream_url",rootData:p,dynamicAnchors:f})||(t=t===null?g.errors:t.concat(g.errors),r=t.length);var c=i===r}else var c=!0;if(c){if(e.detection_url!==void 0){let i=r;g(e.detection_url,{instancePath:s+"/detection_url",parentData:e,parentDataProperty:"detection_url",rootData:p,dynamicAnchors:f})||(t=t===null?g.errors:t.concat(g.errors),r=t.length);var c=i===r}else var c=!0;if(c)if(e.publish_url!==void 0){let i=r;g(e.publish_url,{instancePath:s+"/publish_url",parentData:e,parentDataProperty:"publish_url",rootData:p,dynamicAnchors:f})||(t=t===null?g.errors:t.concat(g.errors),r=t.length);var c=i===r}else var c=!0}}}}}}}}else return F.errors=[{instancePath:s,schemaPath:"#/allOf/1/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;var y=j===r}return F.errors=t,r===0}F.evaluated={props:{can_control_privacy:!0,url:!0,onvif_username:!0,onvif_password:!0,admin_url:!0,go2rtc_source_override:!0,sub_stream_url:!0,detection_url:!0,publish_url:!0,camera_uuid:!0,location_uuid:!0,name:!0,enabled:!0,streaming_enabled:!0,playback_transport:!0,eptz_config:!0,width:!0,height:!0,fps:!0,codec:!0,priority:!0,record:!0,segment_duration:!0,detection_based_recording:!0,detection_model:!0,detection_threshold:!0,detection_interval:!0,pre_detection_buffer:!0,post_detection_buffer:!0,detection_object_filter:!0,detection_object_filter_list:!0,protocol:!0,record_audio:!0,audio_voice_enhancement:!0,isOnvif:!0,backchannel_enabled:!0,retention_days:!0,detection_retention_days:!0,max_storage_mb:!0,tier_critical_multiplier:!0,tier_important_multiplier:!0,tier_ephemeral_multiplier:!0,storage_priority:!0,ptz_enabled:!0,ptz_max_x:!0,ptz_max_y:!0,ptz_max_z:!0,ptz_has_home:!0,onvif_profile:!0,onvif_port:!0,record_on_schedule:!0,recording_schedule:!0,detection_record_on_schedule:!0,detection_recording_schedule:!0,tags:!0,privacy_mode:!0,motion_trigger_source:!0,has_sub_stream:!0,status:!0,error_message:!0,go2rtc_hls_available:!0},dynamicProps:!1,dynamicItems:!1};export{M as validateStreamDetailLiveOnly}; diff --git a/web/js/api/generated/validators/StreamFullResponse.d.mts b/web/js/api/generated/validators/StreamFullResponse.d.mts new file mode 100644 index 000000000..379209903 --- /dev/null +++ b/web/js/api/generated/validators/StreamFullResponse.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateStreamFullResponse } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/StreamFullResponse.mjs b/web/js/api/generated/validators/StreamFullResponse.mjs new file mode 100644 index 000000000..104d33396 --- /dev/null +++ b/web/js/api/generated/validators/StreamFullResponse.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var H=(e,n)=>()=>{try{return n||e((n={exports:{}}).exports,n),n.exports}catch(b){throw n=0,b}};var K=H(W=>{"use strict";Object.defineProperty(W,"__esModule",{value:!0});function Z(e){let n=e.length,b=0,g=0,c;for(;g=55296&&c<=56319&&g36)return N.errors=[{instancePath:n,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:36},message:"must NOT have more than 36 characters"}],!1;if(!J.test(e))return N.errors=[{instancePath:n,schemaPath:"#/pattern",keyword:"pattern",params:{pattern:"^(|[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})$"},message:'must match pattern "^(|[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})$"'}],!1}else return N.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return N.errors=t,r===0}N.evaluated={dynamicProps:!1,dynamicItems:!1};function I(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=I.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(typeof e=="string"){if(x(e)>255)return I.errors=[{instancePath:n,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1;if(x(e)<1)return I.errors=[{instancePath:n,schemaPath:"#/minLength",keyword:"minLength",params:{limit:1},message:"must NOT have fewer than 1 characters"}],!1;if(new TextEncoder().encode(e).byteLength>255)return I.errors=[{instancePath:n,schemaPath:"#/x-lightnvr-max-bytes",keyword:"x-lightnvr-max-bytes",params:{},message:"must fit the UTF-8 byte limit"}],!1}else return I.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return I.errors=t,r===0}I.evaluated={dynamicProps:!1,dynamicItems:!1};var M={type:"string",enum:["auto","webrtc_only","mse_only","hls_only","webrtc_then_mse","mse_then_hls"],$id:"urn:lightnvr:contract:PlaybackTransport"};function T(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=T.evaluated;return o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e!="string"?(T.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1):e==="auto"||e==="webrtc_only"||e==="mse_only"||e==="hls_only"||e==="webrtc_then_mse"||e==="mse_then_hls"?(T.errors=t,r===0):(T.errors=[{instancePath:n,schemaPath:"#/enum",keyword:"enum",params:{allowedValues:M.enum},message:"must be equal to one of the allowed values"}],!1)}T.evaluated={dynamicProps:!1,dynamicItems:!1};function v(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=v.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(e&&typeof e=="object"&&!Array.isArray(e)){let y;if(e.version===void 0&&(y="version")||e.projection===void 0&&(y="projection")||e.mount===void 0&&(y="mount")||e.centerX===void 0&&(y="centerX")||e.centerY===void 0&&(y="centerY")||e.radius===void 0&&(y="radius")||e.fov===void 0&&(y="fov")||e.rotation===void 0&&(y="rotation")||e.defaultYaw===void 0&&(y="defaultYaw")||e.defaultTilt===void 0&&(y="defaultTilt")||e.defaultViewFov===void 0&&(y="defaultViewFov"))return v.errors=[{instancePath:n,schemaPath:"#/required",keyword:"required",params:{missingProperty:y},message:"must have required property '"+y+"'"}],!1;if(e.version!==void 0){let a=r;if(e.version!==1)return v.errors=[{instancePath:n+"/version",schemaPath:"#/properties/version/const",keyword:"const",params:{allowedValue:1},message:"must be equal to constant"}],!1;var p=a===r}else var p=!0;if(p){if(e.projection!==void 0){let a=r;if(e.projection!=="equidistant")return v.errors=[{instancePath:n+"/projection",schemaPath:"#/properties/projection/const",keyword:"const",params:{allowedValue:"equidistant"},message:"must be equal to constant"}],!1;var p=a===r}else var p=!0;if(p){if(e.mount!==void 0){let a=r;if(e.mount!=="ceiling")return v.errors=[{instancePath:n+"/mount",schemaPath:"#/properties/mount/const",keyword:"const",params:{allowedValue:"ceiling"},message:"must be equal to constant"}],!1;var p=a===r}else var p=!0;if(p){if(e.centerX!==void 0){let a=e.centerX,l=r;if(r===l)if(typeof a=="number"&&isFinite(a)){if(a>1||isNaN(a))return v.errors=[{instancePath:n+"/centerX",schemaPath:"#/properties/centerX/maximum",keyword:"maximum",params:{comparison:"<=",limit:1},message:"must be <= 1"}],!1;if(a<0||isNaN(a))return v.errors=[{instancePath:n+"/centerX",schemaPath:"#/properties/centerX/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1}else return v.errors=[{instancePath:n+"/centerX",schemaPath:"#/properties/centerX/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var p=l===r}else var p=!0;if(p){if(e.centerY!==void 0){let a=e.centerY,l=r;if(r===l)if(typeof a=="number"&&isFinite(a)){if(a>1||isNaN(a))return v.errors=[{instancePath:n+"/centerY",schemaPath:"#/properties/centerY/maximum",keyword:"maximum",params:{comparison:"<=",limit:1},message:"must be <= 1"}],!1;if(a<0||isNaN(a))return v.errors=[{instancePath:n+"/centerY",schemaPath:"#/properties/centerY/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1}else return v.errors=[{instancePath:n+"/centerY",schemaPath:"#/properties/centerY/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var p=l===r}else var p=!0;if(p){if(e.radius!==void 0){let a=e.radius,l=r;if(r===l)if(typeof a=="number"&&isFinite(a)){if(a>1||isNaN(a))return v.errors=[{instancePath:n+"/radius",schemaPath:"#/properties/radius/maximum",keyword:"maximum",params:{comparison:"<=",limit:1},message:"must be <= 1"}],!1;if(a<.05||isNaN(a))return v.errors=[{instancePath:n+"/radius",schemaPath:"#/properties/radius/minimum",keyword:"minimum",params:{comparison:">=",limit:.05},message:"must be >= 0.05"}],!1}else return v.errors=[{instancePath:n+"/radius",schemaPath:"#/properties/radius/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var p=l===r}else var p=!0;if(p){if(e.fov!==void 0){let a=e.fov,l=r;if(r===l)if(typeof a=="number"&&isFinite(a)){if(a>360||isNaN(a))return v.errors=[{instancePath:n+"/fov",schemaPath:"#/properties/fov/maximum",keyword:"maximum",params:{comparison:"<=",limit:360},message:"must be <= 360"}],!1;if(a<160||isNaN(a))return v.errors=[{instancePath:n+"/fov",schemaPath:"#/properties/fov/minimum",keyword:"minimum",params:{comparison:">=",limit:160},message:"must be >= 160"}],!1}else return v.errors=[{instancePath:n+"/fov",schemaPath:"#/properties/fov/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var p=l===r}else var p=!0;if(p){if(e.rotation!==void 0){let a=e.rotation,l=r;if(r===l)if(typeof a=="number"&&isFinite(a)){if(a>360||isNaN(a))return v.errors=[{instancePath:n+"/rotation",schemaPath:"#/properties/rotation/maximum",keyword:"maximum",params:{comparison:"<=",limit:360},message:"must be <= 360"}],!1;if(a<-360||isNaN(a))return v.errors=[{instancePath:n+"/rotation",schemaPath:"#/properties/rotation/minimum",keyword:"minimum",params:{comparison:">=",limit:-360},message:"must be >= -360"}],!1}else return v.errors=[{instancePath:n+"/rotation",schemaPath:"#/properties/rotation/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var p=l===r}else var p=!0;if(p){if(e.defaultYaw!==void 0){let a=e.defaultYaw,l=r;if(r===l)if(typeof a=="number"&&isFinite(a)){if(a>360||isNaN(a))return v.errors=[{instancePath:n+"/defaultYaw",schemaPath:"#/properties/defaultYaw/maximum",keyword:"maximum",params:{comparison:"<=",limit:360},message:"must be <= 360"}],!1;if(a<-360||isNaN(a))return v.errors=[{instancePath:n+"/defaultYaw",schemaPath:"#/properties/defaultYaw/minimum",keyword:"minimum",params:{comparison:">=",limit:-360},message:"must be >= -360"}],!1}else return v.errors=[{instancePath:n+"/defaultYaw",schemaPath:"#/properties/defaultYaw/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var p=l===r}else var p=!0;if(p){if(e.defaultTilt!==void 0){let a=e.defaultTilt,l=r;if(r===l)if(typeof a=="number"&&isFinite(a)){if(a>30||isNaN(a))return v.errors=[{instancePath:n+"/defaultTilt",schemaPath:"#/properties/defaultTilt/maximum",keyword:"maximum",params:{comparison:"<=",limit:30},message:"must be <= 30"}],!1;if(a<-90||isNaN(a))return v.errors=[{instancePath:n+"/defaultTilt",schemaPath:"#/properties/defaultTilt/minimum",keyword:"minimum",params:{comparison:">=",limit:-90},message:"must be >= -90"}],!1}else return v.errors=[{instancePath:n+"/defaultTilt",schemaPath:"#/properties/defaultTilt/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var p=l===r}else var p=!0;if(p)if(e.defaultViewFov!==void 0){let a=e.defaultViewFov,l=r;if(r===l)if(typeof a=="number"&&isFinite(a)){if(a>120||isNaN(a))return v.errors=[{instancePath:n+"/defaultViewFov",schemaPath:"#/properties/defaultViewFov/maximum",keyword:"maximum",params:{comparison:"<=",limit:120},message:"must be <= 120"}],!1;if(a<20||isNaN(a))return v.errors=[{instancePath:n+"/defaultViewFov",schemaPath:"#/properties/defaultViewFov/minimum",keyword:"minimum",params:{comparison:">=",limit:20},message:"must be >= 20"}],!1}else return v.errors=[{instancePath:n+"/defaultViewFov",schemaPath:"#/properties/defaultViewFov/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var p=l===r}else var p=!0}}}}}}}}}}else return v.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return v.errors=t,r===0}v.evaluated={props:{version:!0,projection:!0,mount:!0,centerX:!0,centerY:!0,radius:!0,fov:!0,rotation:!0,defaultYaw:!0,defaultTilt:!0,defaultViewFov:!0},dynamicProps:!1,dynamicItems:!1};function F(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=F.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(typeof e=="string"){if(x(e)>1023)return F.errors=[{instancePath:n,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:1023},message:"must NOT have more than 1023 characters"}],!1;if(new TextEncoder().encode(e).byteLength>1023)return F.errors=[{instancePath:n,schemaPath:"#/x-lightnvr-max-bytes",keyword:"x-lightnvr-max-bytes",params:{},message:"must fit the UTF-8 byte limit"}],!1;if(e!==""){let y,a=!0;try{y=JSON.parse(e)}catch{a=!1}if(a){let l=r;v(y,{instancePath:n,parentData:e,parentDataProperty:g,rootData:c,dynamicAnchors:d})||(t=t===null?v.errors:t.concat(v.errors),r=t.length);var p=l===r;a=p}if(!a)return F.errors=[{instancePath:n,schemaPath:"#/contentSchema",keyword:"contentSchema",params:{},message:"must contain valid JSON matching contentSchema (or be empty)"}],!1}}else return F.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return F.errors=t,r===0}F.evaluated={dynamicProps:!1,dynamicItems:!1};function h(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=h.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),!(typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)))return h.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;if(r===0&&typeof e=="number"&&isFinite(e)){if(e>2147483647||isNaN(e))return h.errors=[{instancePath:n,schemaPath:"#/maximum",keyword:"maximum",params:{comparison:"<=",limit:2147483647},message:"must be <= 2147483647"}],!1;if(e<-2147483648||isNaN(e))return h.errors=[{instancePath:n,schemaPath:"#/minimum",keyword:"minimum",params:{comparison:">=",limit:-2147483648},message:"must be >= -2147483648"}],!1}return h.errors=t,r===0}h.evaluated={dynamicProps:!1,dynamicItems:!1};var Q={type:"integer",enum:[0,1],$id:"urn:lightnvr:contract:StreamProtocol"};function E(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=E.evaluated;return o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)?e===0||e===1?(E.errors=t,r===0):(E.errors=[{instancePath:n,schemaPath:"#/enum",keyword:"enum",params:{allowedValues:Q.enum},message:"must be equal to one of the allowed values"}],!1):(E.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1)}E.evaluated={dynamicProps:!1,dynamicItems:!1};function O(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=O.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),!(typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)))return O.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;if(r===0&&typeof e=="number"&&isFinite(e)){if(e>365||isNaN(e))return O.errors=[{instancePath:n,schemaPath:"#/maximum",keyword:"maximum",params:{comparison:"<=",limit:365},message:"must be <= 365"}],!1;if(e<-1||isNaN(e))return O.errors=[{instancePath:n,schemaPath:"#/minimum",keyword:"minimum",params:{comparison:">=",limit:-1},message:"must be >= -1"}],!1}return O.errors=t,r===0}O.evaluated={dynamicProps:!1,dynamicItems:!1};function D(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=D.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(Array.isArray(e)){if(e.length>168)return D.errors=[{instancePath:n,schemaPath:"#/maxItems",keyword:"maxItems",params:{limit:168},message:"must NOT have more than 168 items"}],!1;if(e.length<168)return D.errors=[{instancePath:n,schemaPath:"#/minItems",keyword:"minItems",params:{limit:168},message:"must NOT have fewer than 168 items"}],!1;{var p=!0;let y=e.length;for(let a=0;a511)return _.errors=[{instancePath:n+"/url",schemaPath:"#/properties/url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return _.errors=[{instancePath:n+"/url",schemaPath:"#/properties/url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=f===r}else var i=!0;if(i){if(e.enabled!==void 0){let s=r;if(typeof e.enabled!="boolean")return _.errors=[{instancePath:n+"/enabled",schemaPath:"#/properties/enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.streaming_enabled!==void 0){let s=r;if(typeof e.streaming_enabled!="boolean")return _.errors=[{instancePath:n+"/streaming_enabled",schemaPath:"#/properties/streaming_enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.playback_transport!==void 0){let s=r;T(e.playback_transport,{instancePath:n+"/playback_transport",parentData:e,parentDataProperty:"playback_transport",rootData:c,dynamicAnchors:d})||(t=t===null?T.errors:t.concat(T.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.eptz_config!==void 0){let s=r;F(e.eptz_config,{instancePath:n+"/eptz_config",parentData:e,parentDataProperty:"eptz_config",rootData:c,dynamicAnchors:d})||(t=t===null?F.errors:t.concat(F.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.width!==void 0){let s=r;h(e.width,{instancePath:n+"/width",parentData:e,parentDataProperty:"width",rootData:c,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.height!==void 0){let s=r;h(e.height,{instancePath:n+"/height",parentData:e,parentDataProperty:"height",rootData:c,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.fps!==void 0){let s=r;h(e.fps,{instancePath:n+"/fps",parentData:e,parentDataProperty:"fps",rootData:c,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.codec!==void 0){let s=e.codec,f=r;if(r===f)if(typeof s=="string"){if(x(s)>15)return _.errors=[{instancePath:n+"/codec",schemaPath:"#/properties/codec/maxLength",keyword:"maxLength",params:{limit:15},message:"must NOT have more than 15 characters"}],!1}else return _.errors=[{instancePath:n+"/codec",schemaPath:"#/properties/codec/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=f===r}else var i=!0;if(i){if(e.priority!==void 0){let s=r;h(e.priority,{instancePath:n+"/priority",parentData:e,parentDataProperty:"priority",rootData:c,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.record!==void 0){let s=r;if(typeof e.record!="boolean")return _.errors=[{instancePath:n+"/record",schemaPath:"#/properties/record/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.segment_duration!==void 0){let s=r;h(e.segment_duration,{instancePath:n+"/segment_duration",parentData:e,parentDataProperty:"segment_duration",rootData:c,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.detection_based_recording!==void 0){let s=r;if(typeof e.detection_based_recording!="boolean")return _.errors=[{instancePath:n+"/detection_based_recording",schemaPath:"#/properties/detection_based_recording/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.detection_model!==void 0){let s=e.detection_model,f=r;if(r===f)if(typeof s=="string"){if(x(s)>511)return _.errors=[{instancePath:n+"/detection_model",schemaPath:"#/properties/detection_model/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return _.errors=[{instancePath:n+"/detection_model",schemaPath:"#/properties/detection_model/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=f===r}else var i=!0;if(i){if(e.detection_threshold!==void 0){let s=e.detection_threshold,f=r;if(!(typeof s=="number"&&!(s%1)&&!isNaN(s)&&isFinite(s)))return _.errors=[{instancePath:n+"/detection_threshold",schemaPath:"#/properties/detection_threshold/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;var i=f===r}else var i=!0;if(i){if(e.detection_interval!==void 0){let s=r;h(e.detection_interval,{instancePath:n+"/detection_interval",parentData:e,parentDataProperty:"detection_interval",rootData:c,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.pre_detection_buffer!==void 0){let s=r;h(e.pre_detection_buffer,{instancePath:n+"/pre_detection_buffer",parentData:e,parentDataProperty:"pre_detection_buffer",rootData:c,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.post_detection_buffer!==void 0){let s=r;h(e.post_detection_buffer,{instancePath:n+"/post_detection_buffer",parentData:e,parentDataProperty:"post_detection_buffer",rootData:c,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.detection_object_filter!==void 0){let s=e.detection_object_filter,f=r;if(r===f)if(typeof s=="string"){if(x(s)>15)return _.errors=[{instancePath:n+"/detection_object_filter",schemaPath:"#/properties/detection_object_filter/maxLength",keyword:"maxLength",params:{limit:15},message:"must NOT have more than 15 characters"}],!1}else return _.errors=[{instancePath:n+"/detection_object_filter",schemaPath:"#/properties/detection_object_filter/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=f===r}else var i=!0;if(i){if(e.detection_object_filter_list!==void 0){let s=e.detection_object_filter_list,f=r;if(r===f)if(typeof s=="string"){if(x(s)>255)return _.errors=[{instancePath:n+"/detection_object_filter_list",schemaPath:"#/properties/detection_object_filter_list/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1}else return _.errors=[{instancePath:n+"/detection_object_filter_list",schemaPath:"#/properties/detection_object_filter_list/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=f===r}else var i=!0;if(i){if(e.protocol!==void 0){let s=r;E(e.protocol,{instancePath:n+"/protocol",parentData:e,parentDataProperty:"protocol",rootData:c,dynamicAnchors:d})||(t=t===null?E.errors:t.concat(E.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.record_audio!==void 0){let s=r;if(typeof e.record_audio!="boolean")return _.errors=[{instancePath:n+"/record_audio",schemaPath:"#/properties/record_audio/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.audio_voice_enhancement!==void 0){let s=r;if(typeof e.audio_voice_enhancement!="boolean")return _.errors=[{instancePath:n+"/audio_voice_enhancement",schemaPath:"#/properties/audio_voice_enhancement/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.isOnvif!==void 0){let s=r;if(typeof e.isOnvif!="boolean")return _.errors=[{instancePath:n+"/isOnvif",schemaPath:"#/properties/isOnvif/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.backchannel_enabled!==void 0){let s=r;if(typeof e.backchannel_enabled!="boolean")return _.errors=[{instancePath:n+"/backchannel_enabled",schemaPath:"#/properties/backchannel_enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.retention_days!==void 0){let s=r;O(e.retention_days,{instancePath:n+"/retention_days",parentData:e,parentDataProperty:"retention_days",rootData:c,dynamicAnchors:d})||(t=t===null?O.errors:t.concat(O.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.detection_retention_days!==void 0){let s=r;O(e.detection_retention_days,{instancePath:n+"/detection_retention_days",parentData:e,parentDataProperty:"detection_retention_days",rootData:c,dynamicAnchors:d})||(t=t===null?O.errors:t.concat(O.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.max_storage_mb!==void 0){let s=r;h(e.max_storage_mb,{instancePath:n+"/max_storage_mb",parentData:e,parentDataProperty:"max_storage_mb",rootData:c,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.tier_critical_multiplier!==void 0){let s=e.tier_critical_multiplier,f=r;if(!(typeof s=="number"&&isFinite(s)))return _.errors=[{instancePath:n+"/tier_critical_multiplier",schemaPath:"#/properties/tier_critical_multiplier/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var i=f===r}else var i=!0;if(i){if(e.tier_important_multiplier!==void 0){let s=e.tier_important_multiplier,f=r;if(!(typeof s=="number"&&isFinite(s)))return _.errors=[{instancePath:n+"/tier_important_multiplier",schemaPath:"#/properties/tier_important_multiplier/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var i=f===r}else var i=!0;if(i){if(e.tier_ephemeral_multiplier!==void 0){let s=e.tier_ephemeral_multiplier,f=r;if(!(typeof s=="number"&&isFinite(s)))return _.errors=[{instancePath:n+"/tier_ephemeral_multiplier",schemaPath:"#/properties/tier_ephemeral_multiplier/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var i=f===r}else var i=!0;if(i){if(e.storage_priority!==void 0){let s=r;h(e.storage_priority,{instancePath:n+"/storage_priority",parentData:e,parentDataProperty:"storage_priority",rootData:c,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.ptz_enabled!==void 0){let s=r;if(typeof e.ptz_enabled!="boolean")return _.errors=[{instancePath:n+"/ptz_enabled",schemaPath:"#/properties/ptz_enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.ptz_max_x!==void 0){let s=r;h(e.ptz_max_x,{instancePath:n+"/ptz_max_x",parentData:e,parentDataProperty:"ptz_max_x",rootData:c,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.ptz_max_y!==void 0){let s=r;h(e.ptz_max_y,{instancePath:n+"/ptz_max_y",parentData:e,parentDataProperty:"ptz_max_y",rootData:c,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.ptz_max_z!==void 0){let s=r;h(e.ptz_max_z,{instancePath:n+"/ptz_max_z",parentData:e,parentDataProperty:"ptz_max_z",rootData:c,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.ptz_has_home!==void 0){let s=r;if(typeof e.ptz_has_home!="boolean")return _.errors=[{instancePath:n+"/ptz_has_home",schemaPath:"#/properties/ptz_has_home/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.onvif_username!==void 0){let s=e.onvif_username,f=r;if(r===f)if(typeof s=="string"){if(x(s)>63)return _.errors=[{instancePath:n+"/onvif_username",schemaPath:"#/properties/onvif_username/maxLength",keyword:"maxLength",params:{limit:63},message:"must NOT have more than 63 characters"}],!1}else return _.errors=[{instancePath:n+"/onvif_username",schemaPath:"#/properties/onvif_username/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=f===r}else var i=!0;if(i){if(e.onvif_password!==void 0){let s=e.onvif_password,f=r;if(r===f)if(typeof s=="string"){if(x(s)>63)return _.errors=[{instancePath:n+"/onvif_password",schemaPath:"#/properties/onvif_password/maxLength",keyword:"maxLength",params:{limit:63},message:"must NOT have more than 63 characters"}],!1}else return _.errors=[{instancePath:n+"/onvif_password",schemaPath:"#/properties/onvif_password/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=f===r}else var i=!0;if(i){if(e.onvif_profile!==void 0){let s=e.onvif_profile,f=r;if(r===f)if(typeof s=="string"){if(x(s)>63)return _.errors=[{instancePath:n+"/onvif_profile",schemaPath:"#/properties/onvif_profile/maxLength",keyword:"maxLength",params:{limit:63},message:"must NOT have more than 63 characters"}],!1}else return _.errors=[{instancePath:n+"/onvif_profile",schemaPath:"#/properties/onvif_profile/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=f===r}else var i=!0;if(i){if(e.onvif_port!==void 0){let s=r;h(e.onvif_port,{instancePath:n+"/onvif_port",parentData:e,parentDataProperty:"onvif_port",rootData:c,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.record_on_schedule!==void 0){let s=r;if(typeof e.record_on_schedule!="boolean")return _.errors=[{instancePath:n+"/record_on_schedule",schemaPath:"#/properties/record_on_schedule/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.recording_schedule!==void 0){let s=r;D(e.recording_schedule,{instancePath:n+"/recording_schedule",parentData:e,parentDataProperty:"recording_schedule",rootData:c,dynamicAnchors:d})||(t=t===null?D.errors:t.concat(D.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.detection_record_on_schedule!==void 0){let s=r;if(typeof e.detection_record_on_schedule!="boolean")return _.errors=[{instancePath:n+"/detection_record_on_schedule",schemaPath:"#/properties/detection_record_on_schedule/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.detection_recording_schedule!==void 0){let s=r;D(e.detection_recording_schedule,{instancePath:n+"/detection_recording_schedule",parentData:e,parentDataProperty:"detection_recording_schedule",rootData:c,dynamicAnchors:d})||(t=t===null?D.errors:t.concat(D.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.tags!==void 0){let s=e.tags,f=r;if(r===f)if(typeof s=="string"){if(x(s)>255)return _.errors=[{instancePath:n+"/tags",schemaPath:"#/properties/tags/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1}else return _.errors=[{instancePath:n+"/tags",schemaPath:"#/properties/tags/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=f===r}else var i=!0;if(i){if(e.admin_url!==void 0){let s=e.admin_url,f=r;if(r===f)if(typeof s=="string"){if(x(s)>511)return _.errors=[{instancePath:n+"/admin_url",schemaPath:"#/properties/admin_url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return _.errors=[{instancePath:n+"/admin_url",schemaPath:"#/properties/admin_url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=f===r}else var i=!0;if(i){if(e.privacy_mode!==void 0){let s=r;if(typeof e.privacy_mode!="boolean")return _.errors=[{instancePath:n+"/privacy_mode",schemaPath:"#/properties/privacy_mode/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.can_control_privacy!==void 0){let s=r;if(typeof e.can_control_privacy!="boolean")return _.errors=[{instancePath:n+"/can_control_privacy",schemaPath:"#/properties/can_control_privacy/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.motion_trigger_source!==void 0){let s=e.motion_trigger_source,f=r;if(r===f)if(typeof s=="string"){if(x(s)>255)return _.errors=[{instancePath:n+"/motion_trigger_source",schemaPath:"#/properties/motion_trigger_source/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1}else return _.errors=[{instancePath:n+"/motion_trigger_source",schemaPath:"#/properties/motion_trigger_source/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=f===r}else var i=!0;if(i){if(e.go2rtc_source_override!==void 0){let s=e.go2rtc_source_override,f=r;if(r===f)if(typeof s=="string"){if(x(s)>8191)return _.errors=[{instancePath:n+"/go2rtc_source_override",schemaPath:"#/properties/go2rtc_source_override/maxLength",keyword:"maxLength",params:{limit:8191},message:"must NOT have more than 8191 characters"}],!1}else return _.errors=[{instancePath:n+"/go2rtc_source_override",schemaPath:"#/properties/go2rtc_source_override/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=f===r}else var i=!0;if(i){if(e.sub_stream_url!==void 0){let s=e.sub_stream_url,f=r;if(r===f)if(typeof s=="string"){if(x(s)>511)return _.errors=[{instancePath:n+"/sub_stream_url",schemaPath:"#/properties/sub_stream_url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return _.errors=[{instancePath:n+"/sub_stream_url",schemaPath:"#/properties/sub_stream_url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=f===r}else var i=!0;if(i){if(e.has_sub_stream!==void 0){let s=r;if(typeof e.has_sub_stream!="boolean")return _.errors=[{instancePath:n+"/has_sub_stream",schemaPath:"#/properties/has_sub_stream/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.detection_url!==void 0){let s=e.detection_url,f=r;if(r===f)if(typeof s=="string"){if(x(s)>511)return _.errors=[{instancePath:n+"/detection_url",schemaPath:"#/properties/detection_url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return _.errors=[{instancePath:n+"/detection_url",schemaPath:"#/properties/detection_url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=f===r}else var i=!0;if(i){if(e.publish_url!==void 0){let s=e.publish_url,f=r;if(r===f)if(typeof s=="string"){if(x(s)>511)return _.errors=[{instancePath:n+"/publish_url",schemaPath:"#/properties/publish_url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return _.errors=[{instancePath:n+"/publish_url",schemaPath:"#/properties/publish_url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=f===r}else var i=!0;if(i){if(e.status!==void 0){let s=r;A(e.status,{instancePath:n+"/status",parentData:e,parentDataProperty:"status",rootData:c,dynamicAnchors:d})||(t=t===null?A.errors:t.concat(A.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.error_message!==void 0){let s=e.error_message,f=r;if(r===f)if(typeof s=="string"){if(x(s)>255)return _.errors=[{instancePath:n+"/error_message",schemaPath:"#/properties/error_message/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1;if(x(s)<1)return _.errors=[{instancePath:n+"/error_message",schemaPath:"#/properties/error_message/minLength",keyword:"minLength",params:{limit:1},message:"must NOT have fewer than 1 characters"}],!1}else return _.errors=[{instancePath:n+"/error_message",schemaPath:"#/properties/error_message/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=f===r}else var i=!0;if(i)if(e.go2rtc_hls_available!==void 0){let s=r;if(typeof e.go2rtc_hls_available!="boolean")return _.errors=[{instancePath:n+"/go2rtc_hls_available",schemaPath:"#/properties/go2rtc_hls_available/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}else return _.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return _.errors=t,r===0}_.evaluated={props:{camera_uuid:!0,location_uuid:!0,name:!0,url:!0,enabled:!0,streaming_enabled:!0,playback_transport:!0,eptz_config:!0,width:!0,height:!0,fps:!0,codec:!0,priority:!0,record:!0,segment_duration:!0,detection_based_recording:!0,detection_model:!0,detection_threshold:!0,detection_interval:!0,pre_detection_buffer:!0,post_detection_buffer:!0,detection_object_filter:!0,detection_object_filter_list:!0,protocol:!0,record_audio:!0,audio_voice_enhancement:!0,isOnvif:!0,backchannel_enabled:!0,retention_days:!0,detection_retention_days:!0,max_storage_mb:!0,tier_critical_multiplier:!0,tier_important_multiplier:!0,tier_ephemeral_multiplier:!0,storage_priority:!0,ptz_enabled:!0,ptz_max_x:!0,ptz_max_y:!0,ptz_max_z:!0,ptz_has_home:!0,onvif_username:!0,onvif_password:!0,onvif_profile:!0,onvif_port:!0,record_on_schedule:!0,recording_schedule:!0,detection_record_on_schedule:!0,detection_recording_schedule:!0,tags:!0,admin_url:!0,privacy_mode:!0,can_control_privacy:!0,motion_trigger_source:!0,go2rtc_source_override:!0,sub_stream_url:!0,has_sub_stream:!0,detection_url:!0,publish_url:!0,status:!0,error_message:!0,go2rtc_hls_available:!0},dynamicProps:!1,dynamicItems:!1};var ee=new RegExp("^(?![A-Za-z][A-Za-z0-9+.-]*://[^/?#]*@)","u");function L(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=L.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(typeof e=="string"){if(x(e)>511)return L.errors=[{instancePath:n,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1;if(!ee.test(e))return L.errors=[{instancePath:n,schemaPath:"#/pattern",keyword:"pattern",params:{pattern:"^(?![A-Za-z][A-Za-z0-9+.-]*://[^/?#]*@)"},message:'must match pattern "^(?![A-Za-z][A-Za-z0-9+.-]*://[^/?#]*@)"'}],!1}else return L.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return L.errors=t,r===0}L.evaluated={dynamicProps:!1,dynamicItems:!1};function j(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=j.evaluated;o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0);let p=r;_(e,{instancePath:n,parentData:b,parentDataProperty:g,rootData:c,dynamicAnchors:d})||(t=t===null?_.errors:t.concat(_.errors),r=t.length);var y=p===r;if(y){let $=r,i=r,m=!0,s=r,f=r,z=!1,X=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let P;if(e.isOnvif===void 0&&(P="isOnvif")){let k={};t===null?t=[k]:t.push(k),r++}else if(e.isOnvif!==void 0&&e.isOnvif!==!0){let k={};t===null?t=[k]:t.push(k),r++}}var l=X===r;if(z=z||l,l){var a={};a.isOnvif=!0}let C=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let P;if(e.onvif_username===void 0&&(P="onvif_username")){let k={};t===null?t=[k]:t.push(k),r++}else if(e.onvif_username!==void 0){let k=e.onvif_username;if(typeof k=="string"&&x(k)<1){let R={};t===null?t=[R]:t.push(R),r++}}}var l=C===r;z=z||l,l&&a!==!0&&(a=a||{},a.onvif_username=!0);let B=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let P;if(e.onvif_password===void 0&&(P="onvif_password")){let k={};t===null?t=[k]:t.push(k),r++}else if(e.onvif_password!==void 0){let k=e.onvif_password;if(typeof k=="string"&&x(k)<1){let R={};t===null?t=[R]:t.push(R),r++}}}var l=B===r;if(z=z||l,l&&a!==!0&&(a=a||{},a.onvif_password=!0),z)r=f,t!==null&&(f?t.length=f:t=null);else{let P={};t===null?t=[P]:t.push(P),r++}var u=s===r;if(r=i,t!==null&&(i?t.length=i:t=null),u){let P=r;e&&typeof e=="object"&&!Array.isArray(e)&&e.url!==void 0&&(L(e.url,{instancePath:n+"/url",parentData:e,parentDataProperty:"url",rootData:c,dynamicAnchors:d})||(t=t===null?L.errors:t.concat(L.errors),r=t.length));var u=P===r;m=u,m&&a!==!0&&(a=a||{},a.url=!0)}if(!m){let P={instancePath:n,schemaPath:"#/allOf/1/if",keyword:"if",params:{failingKeyword:"then"},message:'must match "then" schema'};return t===null?t=[P]:t.push(P),r++,j.errors=t,!1}if(r===$)if(e&&typeof e=="object"&&!Array.isArray(e)){let P;if(e.url===void 0&&(P="url")||e.onvif_username===void 0&&(P="onvif_username")||e.onvif_password===void 0&&(P="onvif_password")||e.admin_url===void 0&&(P="admin_url")||e.go2rtc_source_override===void 0&&(P="go2rtc_source_override")||e.sub_stream_url===void 0&&(P="sub_stream_url")||e.detection_url===void 0&&(P="detection_url")||e.publish_url===void 0&&(P="publish_url"))return j.errors=[{instancePath:n,schemaPath:"#/allOf/1/required",keyword:"required",params:{missingProperty:P},message:"must have required property '"+P+"'"}],!1;if(a!==!0&&(a=a||{},a.can_control_privacy=!0),e.can_control_privacy!==void 0&&e.can_control_privacy!==!0)return j.errors=[{instancePath:n+"/can_control_privacy",schemaPath:"#/allOf/1/properties/can_control_privacy/const",keyword:"const",params:{allowedValue:!0},message:"must be equal to constant"}],!1}else return j.errors=[{instancePath:n,schemaPath:"#/allOf/1/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;var y=$===r;y&&a!==!0&&(a=a||{},a.camera_uuid=!0,a.location_uuid=!0,a.name=!0,a.url=!0,a.enabled=!0,a.streaming_enabled=!0,a.playback_transport=!0,a.eptz_config=!0,a.width=!0,a.height=!0,a.fps=!0,a.codec=!0,a.priority=!0,a.record=!0,a.segment_duration=!0,a.detection_based_recording=!0,a.detection_model=!0,a.detection_threshold=!0,a.detection_interval=!0,a.pre_detection_buffer=!0,a.post_detection_buffer=!0,a.detection_object_filter=!0,a.detection_object_filter_list=!0,a.protocol=!0,a.record_audio=!0,a.audio_voice_enhancement=!0,a.isOnvif=!0,a.backchannel_enabled=!0,a.retention_days=!0,a.detection_retention_days=!0,a.max_storage_mb=!0,a.tier_critical_multiplier=!0,a.tier_important_multiplier=!0,a.tier_ephemeral_multiplier=!0,a.storage_priority=!0,a.ptz_enabled=!0,a.ptz_max_x=!0,a.ptz_max_y=!0,a.ptz_max_z=!0,a.ptz_has_home=!0,a.onvif_username=!0,a.onvif_password=!0,a.onvif_profile=!0,a.onvif_port=!0,a.record_on_schedule=!0,a.recording_schedule=!0,a.detection_record_on_schedule=!0,a.detection_recording_schedule=!0,a.tags=!0,a.admin_url=!0,a.privacy_mode=!0,a.can_control_privacy=!0,a.motion_trigger_source=!0,a.go2rtc_source_override=!0,a.sub_stream_url=!0,a.has_sub_stream=!0,a.detection_url=!0,a.publish_url=!0,a.status=!0,a.error_message=!0,a.go2rtc_hls_available=!0)}return j.errors=t,o.props=a,r===0}j.evaluated={dynamicProps:!0,dynamicItems:!1};var re=new RegExp("[?&;][^=&#]*([Pp][Aa][Ss][Ss]|[Pp][Ww][Dd]|[Tt][Oo][Kk][Ee][Nn]|[Ss][Ee][Cc][Rr][Ee][Tt]|[Aa][Pp][Ii]_?[Kk][Ee][Yy])[^=&#]*=[^&#]","u");function q(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=q.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e!="string")return q.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;let p=r,y=r;if(typeof e=="string"&&!re.test(e)){let l={};t===null?t=[l]:t.push(l),r++}var a=y===r;return a?(q.errors=[{instancePath:n,schemaPath:"#/not",keyword:"not",params:{},message:"must NOT be valid"}],!1):(r=p,t!==null&&(p?t.length=p:t=null),q.errors=t,r===0)}q.evaluated={dynamicProps:!1,dynamicItems:!1};function U(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=U.evaluated;o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0);let p=r;L(e,{instancePath:n,parentData:b,parentDataProperty:g,rootData:c,dynamicAnchors:d})||(t=t===null?L.errors:t.concat(L.errors),r=t.length);var y=p===r;if(y){let a=r;q(e,{instancePath:n,parentData:b,parentDataProperty:g,rootData:c,dynamicAnchors:d})||(t=t===null?q.errors:t.concat(q.errors),r=t.length);var y=a===r}return U.errors=t,r===0}U.evaluated={dynamicProps:!1,dynamicItems:!1};function w(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=w.evaluated;return o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e!="string"?(w.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1):e!==""?(w.errors=[{instancePath:n,schemaPath:"#/const",keyword:"const",params:{allowedValue:""},message:"must be equal to constant"}],!1):(w.errors=t,r===0)}w.evaluated={dynamicProps:!1,dynamicItems:!1};function S(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=S.evaluated;o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0);let p=r;_(e,{instancePath:n,parentData:b,parentDataProperty:g,rootData:c,dynamicAnchors:d})||(t=t===null?_.errors:t.concat(_.errors),r=t.length);var y=p===r;if(y){let u=r,$=r,i=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let s;if(e.detection_api_url===void 0&&(s="detection_api_url")){let f={};t===null?t=[f]:t.push(f),r++}}var a=i===r;if(a)return S.errors=[{instancePath:n,schemaPath:"#/allOf/1/not",keyword:"not",params:{},message:"must NOT be valid"}],!1;if(r=$,t!==null&&($?t.length=$:t=null),r===u)if(e&&typeof e=="object"&&!Array.isArray(e)){if(e.can_control_privacy!==void 0){let s=r;if(e.can_control_privacy!==!1)return S.errors=[{instancePath:n+"/can_control_privacy",schemaPath:"#/allOf/1/properties/can_control_privacy/const",keyword:"const",params:{allowedValue:!1},message:"must be equal to constant"}],!1;var l=s===r}else var l=!0;if(l){if(e.url!==void 0){let s=r;U(e.url,{instancePath:n+"/url",parentData:e,parentDataProperty:"url",rootData:c,dynamicAnchors:d})||(t=t===null?U.errors:t.concat(U.errors),r=t.length);var l=s===r}else var l=!0;if(l){if(e.onvif_username!==void 0){let s=r;w(e.onvif_username,{instancePath:n+"/onvif_username",parentData:e,parentDataProperty:"onvif_username",rootData:c,dynamicAnchors:d})||(t=t===null?w.errors:t.concat(w.errors),r=t.length);var l=s===r}else var l=!0;if(l){if(e.onvif_password!==void 0){let s=r;w(e.onvif_password,{instancePath:n+"/onvif_password",parentData:e,parentDataProperty:"onvif_password",rootData:c,dynamicAnchors:d})||(t=t===null?w.errors:t.concat(w.errors),r=t.length);var l=s===r}else var l=!0;if(l){if(e.admin_url!==void 0){let s=r;w(e.admin_url,{instancePath:n+"/admin_url",parentData:e,parentDataProperty:"admin_url",rootData:c,dynamicAnchors:d})||(t=t===null?w.errors:t.concat(w.errors),r=t.length);var l=s===r}else var l=!0;if(l){if(e.go2rtc_source_override!==void 0){let s=r;w(e.go2rtc_source_override,{instancePath:n+"/go2rtc_source_override",parentData:e,parentDataProperty:"go2rtc_source_override",rootData:c,dynamicAnchors:d})||(t=t===null?w.errors:t.concat(w.errors),r=t.length);var l=s===r}else var l=!0;if(l){if(e.sub_stream_url!==void 0){let s=r;w(e.sub_stream_url,{instancePath:n+"/sub_stream_url",parentData:e,parentDataProperty:"sub_stream_url",rootData:c,dynamicAnchors:d})||(t=t===null?w.errors:t.concat(w.errors),r=t.length);var l=s===r}else var l=!0;if(l){if(e.detection_url!==void 0){let s=r;w(e.detection_url,{instancePath:n+"/detection_url",parentData:e,parentDataProperty:"detection_url",rootData:c,dynamicAnchors:d})||(t=t===null?w.errors:t.concat(w.errors),r=t.length);var l=s===r}else var l=!0;if(l)if(e.publish_url!==void 0){let s=r;w(e.publish_url,{instancePath:n+"/publish_url",parentData:e,parentDataProperty:"publish_url",rootData:c,dynamicAnchors:d})||(t=t===null?w.errors:t.concat(w.errors),r=t.length);var l=s===r}else var l=!0}}}}}}}}else return S.errors=[{instancePath:n,schemaPath:"#/allOf/1/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;var y=u===r}return S.errors=t,r===0}S.evaluated={props:{can_control_privacy:!0,url:!0,onvif_username:!0,onvif_password:!0,admin_url:!0,go2rtc_source_override:!0,sub_stream_url:!0,detection_url:!0,publish_url:!0,camera_uuid:!0,location_uuid:!0,name:!0,enabled:!0,streaming_enabled:!0,playback_transport:!0,eptz_config:!0,width:!0,height:!0,fps:!0,codec:!0,priority:!0,record:!0,segment_duration:!0,detection_based_recording:!0,detection_model:!0,detection_threshold:!0,detection_interval:!0,pre_detection_buffer:!0,post_detection_buffer:!0,detection_object_filter:!0,detection_object_filter_list:!0,protocol:!0,record_audio:!0,audio_voice_enhancement:!0,isOnvif:!0,backchannel_enabled:!0,retention_days:!0,detection_retention_days:!0,max_storage_mb:!0,tier_critical_multiplier:!0,tier_important_multiplier:!0,tier_ephemeral_multiplier:!0,storage_priority:!0,ptz_enabled:!0,ptz_max_x:!0,ptz_max_y:!0,ptz_max_z:!0,ptz_has_home:!0,onvif_profile:!0,onvif_port:!0,record_on_schedule:!0,recording_schedule:!0,detection_record_on_schedule:!0,detection_recording_schedule:!0,tags:!0,privacy_mode:!0,motion_trigger_source:!0,has_sub_stream:!0,status:!0,error_message:!0,go2rtc_hls_available:!0},dynamicProps:!1,dynamicItems:!1};function Y(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=Y.evaluated;o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0);let p=r,y=!1,a=null,l=r;if(!j(e,{instancePath:n,parentData:b,parentDataProperty:g,rootData:c,dynamicAnchors:d}))t=t===null?j.errors:t.concat(j.errors),r=t.length;else var u=j.evaluated.props;var i=l===r;i&&(y=!0,a=0);let $=r;S(e,{instancePath:n,parentData:b,parentDataProperty:g,rootData:c,dynamicAnchors:d})||(t=t===null?S.errors:t.concat(S.errors),r=t.length);var i=$===r;if(i&&y?(y=!1,a=[a,1]):i&&(y=!0,a=1,u!==!0&&(u=u||{},u.can_control_privacy=!0,u.url=!0,u.onvif_username=!0,u.onvif_password=!0,u.admin_url=!0,u.go2rtc_source_override=!0,u.sub_stream_url=!0,u.detection_url=!0,u.publish_url=!0,u.camera_uuid=!0,u.location_uuid=!0,u.name=!0,u.enabled=!0,u.streaming_enabled=!0,u.playback_transport=!0,u.eptz_config=!0,u.width=!0,u.height=!0,u.fps=!0,u.codec=!0,u.priority=!0,u.record=!0,u.segment_duration=!0,u.detection_based_recording=!0,u.detection_model=!0,u.detection_threshold=!0,u.detection_interval=!0,u.pre_detection_buffer=!0,u.post_detection_buffer=!0,u.detection_object_filter=!0,u.detection_object_filter_list=!0,u.protocol=!0,u.record_audio=!0,u.audio_voice_enhancement=!0,u.isOnvif=!0,u.backchannel_enabled=!0,u.retention_days=!0,u.detection_retention_days=!0,u.max_storage_mb=!0,u.tier_critical_multiplier=!0,u.tier_important_multiplier=!0,u.tier_ephemeral_multiplier=!0,u.storage_priority=!0,u.ptz_enabled=!0,u.ptz_max_x=!0,u.ptz_max_y=!0,u.ptz_max_z=!0,u.ptz_has_home=!0,u.onvif_profile=!0,u.onvif_port=!0,u.record_on_schedule=!0,u.recording_schedule=!0,u.detection_record_on_schedule=!0,u.detection_recording_schedule=!0,u.tags=!0,u.privacy_mode=!0,u.motion_trigger_source=!0,u.has_sub_stream=!0,u.status=!0,u.error_message=!0,u.go2rtc_hls_available=!0)),y)r=p,t!==null&&(p?t.length=p:t=null);else{let m={instancePath:n,schemaPath:"#/oneOf",keyword:"oneOf",params:{passingSchemas:a},message:"must match exactly one schema in oneOf"};return t===null?t=[m]:t.push(m),r++,Y.errors=t,!1}return Y.errors=t,o.props=u,r===0}Y.evaluated={dynamicProps:!0,dynamicItems:!1};function V(e,{instancePath:n="",parentData:b,parentDataProperty:g,rootData:c=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=V.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(e&&typeof e=="object"&&!Array.isArray(e)){let p;if(e.stream===void 0&&(p="stream"))return V.errors=[{instancePath:n,schemaPath:"#/required",keyword:"required",params:{missingProperty:p},message:"must have required property '"+p+"'"}],!1;e.stream!==void 0&&(Y(e.stream,{instancePath:n+"/stream",parentData:e,parentDataProperty:"stream",rootData:c,dynamicAnchors:d})||(t=t===null?Y.errors:t.concat(Y.errors),r=t.length))}else return V.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return V.errors=t,r===0}V.evaluated={props:{stream:!0},dynamicProps:!1,dynamicItems:!1};export{ie as validateStreamFullResponse}; diff --git a/web/js/api/generated/validators/StreamFullResponseConfigurable.d.mts b/web/js/api/generated/validators/StreamFullResponseConfigurable.d.mts new file mode 100644 index 000000000..8af01433e --- /dev/null +++ b/web/js/api/generated/validators/StreamFullResponseConfigurable.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateStreamFullResponseConfigurable } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/StreamFullResponseConfigurable.mjs b/web/js/api/generated/validators/StreamFullResponseConfigurable.mjs new file mode 100644 index 000000000..f2c0b4d4e --- /dev/null +++ b/web/js/api/generated/validators/StreamFullResponseConfigurable.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var U=(e,s)=>()=>{try{return s||e((s={exports:{}}).exports,s),s.exports}catch(w){throw s=0,w}};var X=U(V=>{"use strict";Object.defineProperty(V,"__esModule",{value:!0});function C(e){let s=e.length,w=0,b=0,f;for(;b=55296&&f<=56319&&b36)return z.errors=[{instancePath:s,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:36},message:"must NOT have more than 36 characters"}],!1;if(!Z.test(e))return z.errors=[{instancePath:s,schemaPath:"#/pattern",keyword:"pattern",params:{pattern:"^(|[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})$"},message:'must match pattern "^(|[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})$"'}],!1}else return z.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return z.errors=t,r===0}z.evaluated={dynamicProps:!1,dynamicItems:!1};function D(e,{instancePath:s="",parentData:w,parentDataProperty:b,rootData:f=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=D.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(typeof e=="string"){if(v(e)>255)return D.errors=[{instancePath:s,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1;if(v(e)<1)return D.errors=[{instancePath:s,schemaPath:"#/minLength",keyword:"minLength",params:{limit:1},message:"must NOT have fewer than 1 characters"}],!1;if(new TextEncoder().encode(e).byteLength>255)return D.errors=[{instancePath:s,schemaPath:"#/x-lightnvr-max-bytes",keyword:"x-lightnvr-max-bytes",params:{},message:"must fit the UTF-8 byte limit"}],!1}else return D.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return D.errors=t,r===0}D.evaluated={dynamicProps:!1,dynamicItems:!1};var W={type:"string",enum:["auto","webrtc_only","mse_only","hls_only","webrtc_then_mse","mse_then_hls"],$id:"urn:lightnvr:contract:PlaybackTransport"};function F(e,{instancePath:s="",parentData:w,parentDataProperty:b,rootData:f=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=F.evaluated;return o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e!="string"?(F.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1):e==="auto"||e==="webrtc_only"||e==="mse_only"||e==="hls_only"||e==="webrtc_then_mse"||e==="mse_then_hls"?(F.errors=t,r===0):(F.errors=[{instancePath:s,schemaPath:"#/enum",keyword:"enum",params:{allowedValues:W.enum},message:"must be equal to one of the allowed values"}],!1)}F.evaluated={dynamicProps:!1,dynamicItems:!1};function h(e,{instancePath:s="",parentData:w,parentDataProperty:b,rootData:f=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=h.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(e&&typeof e=="object"&&!Array.isArray(e)){let y;if(e.version===void 0&&(y="version")||e.projection===void 0&&(y="projection")||e.mount===void 0&&(y="mount")||e.centerX===void 0&&(y="centerX")||e.centerY===void 0&&(y="centerY")||e.radius===void 0&&(y="radius")||e.fov===void 0&&(y="fov")||e.rotation===void 0&&(y="rotation")||e.defaultYaw===void 0&&(y="defaultYaw")||e.defaultTilt===void 0&&(y="defaultTilt")||e.defaultViewFov===void 0&&(y="defaultViewFov"))return h.errors=[{instancePath:s,schemaPath:"#/required",keyword:"required",params:{missingProperty:y},message:"must have required property '"+y+"'"}],!1;if(e.version!==void 0){let a=r;if(e.version!==1)return h.errors=[{instancePath:s+"/version",schemaPath:"#/properties/version/const",keyword:"const",params:{allowedValue:1},message:"must be equal to constant"}],!1;var p=a===r}else var p=!0;if(p){if(e.projection!==void 0){let a=r;if(e.projection!=="equidistant")return h.errors=[{instancePath:s+"/projection",schemaPath:"#/properties/projection/const",keyword:"const",params:{allowedValue:"equidistant"},message:"must be equal to constant"}],!1;var p=a===r}else var p=!0;if(p){if(e.mount!==void 0){let a=r;if(e.mount!=="ceiling")return h.errors=[{instancePath:s+"/mount",schemaPath:"#/properties/mount/const",keyword:"const",params:{allowedValue:"ceiling"},message:"must be equal to constant"}],!1;var p=a===r}else var p=!0;if(p){if(e.centerX!==void 0){let a=e.centerX,_=r;if(r===_)if(typeof a=="number"&&isFinite(a)){if(a>1||isNaN(a))return h.errors=[{instancePath:s+"/centerX",schemaPath:"#/properties/centerX/maximum",keyword:"maximum",params:{comparison:"<=",limit:1},message:"must be <= 1"}],!1;if(a<0||isNaN(a))return h.errors=[{instancePath:s+"/centerX",schemaPath:"#/properties/centerX/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1}else return h.errors=[{instancePath:s+"/centerX",schemaPath:"#/properties/centerX/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var p=_===r}else var p=!0;if(p){if(e.centerY!==void 0){let a=e.centerY,_=r;if(r===_)if(typeof a=="number"&&isFinite(a)){if(a>1||isNaN(a))return h.errors=[{instancePath:s+"/centerY",schemaPath:"#/properties/centerY/maximum",keyword:"maximum",params:{comparison:"<=",limit:1},message:"must be <= 1"}],!1;if(a<0||isNaN(a))return h.errors=[{instancePath:s+"/centerY",schemaPath:"#/properties/centerY/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1}else return h.errors=[{instancePath:s+"/centerY",schemaPath:"#/properties/centerY/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var p=_===r}else var p=!0;if(p){if(e.radius!==void 0){let a=e.radius,_=r;if(r===_)if(typeof a=="number"&&isFinite(a)){if(a>1||isNaN(a))return h.errors=[{instancePath:s+"/radius",schemaPath:"#/properties/radius/maximum",keyword:"maximum",params:{comparison:"<=",limit:1},message:"must be <= 1"}],!1;if(a<.05||isNaN(a))return h.errors=[{instancePath:s+"/radius",schemaPath:"#/properties/radius/minimum",keyword:"minimum",params:{comparison:">=",limit:.05},message:"must be >= 0.05"}],!1}else return h.errors=[{instancePath:s+"/radius",schemaPath:"#/properties/radius/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var p=_===r}else var p=!0;if(p){if(e.fov!==void 0){let a=e.fov,_=r;if(r===_)if(typeof a=="number"&&isFinite(a)){if(a>360||isNaN(a))return h.errors=[{instancePath:s+"/fov",schemaPath:"#/properties/fov/maximum",keyword:"maximum",params:{comparison:"<=",limit:360},message:"must be <= 360"}],!1;if(a<160||isNaN(a))return h.errors=[{instancePath:s+"/fov",schemaPath:"#/properties/fov/minimum",keyword:"minimum",params:{comparison:">=",limit:160},message:"must be >= 160"}],!1}else return h.errors=[{instancePath:s+"/fov",schemaPath:"#/properties/fov/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var p=_===r}else var p=!0;if(p){if(e.rotation!==void 0){let a=e.rotation,_=r;if(r===_)if(typeof a=="number"&&isFinite(a)){if(a>360||isNaN(a))return h.errors=[{instancePath:s+"/rotation",schemaPath:"#/properties/rotation/maximum",keyword:"maximum",params:{comparison:"<=",limit:360},message:"must be <= 360"}],!1;if(a<-360||isNaN(a))return h.errors=[{instancePath:s+"/rotation",schemaPath:"#/properties/rotation/minimum",keyword:"minimum",params:{comparison:">=",limit:-360},message:"must be >= -360"}],!1}else return h.errors=[{instancePath:s+"/rotation",schemaPath:"#/properties/rotation/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var p=_===r}else var p=!0;if(p){if(e.defaultYaw!==void 0){let a=e.defaultYaw,_=r;if(r===_)if(typeof a=="number"&&isFinite(a)){if(a>360||isNaN(a))return h.errors=[{instancePath:s+"/defaultYaw",schemaPath:"#/properties/defaultYaw/maximum",keyword:"maximum",params:{comparison:"<=",limit:360},message:"must be <= 360"}],!1;if(a<-360||isNaN(a))return h.errors=[{instancePath:s+"/defaultYaw",schemaPath:"#/properties/defaultYaw/minimum",keyword:"minimum",params:{comparison:">=",limit:-360},message:"must be >= -360"}],!1}else return h.errors=[{instancePath:s+"/defaultYaw",schemaPath:"#/properties/defaultYaw/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var p=_===r}else var p=!0;if(p){if(e.defaultTilt!==void 0){let a=e.defaultTilt,_=r;if(r===_)if(typeof a=="number"&&isFinite(a)){if(a>30||isNaN(a))return h.errors=[{instancePath:s+"/defaultTilt",schemaPath:"#/properties/defaultTilt/maximum",keyword:"maximum",params:{comparison:"<=",limit:30},message:"must be <= 30"}],!1;if(a<-90||isNaN(a))return h.errors=[{instancePath:s+"/defaultTilt",schemaPath:"#/properties/defaultTilt/minimum",keyword:"minimum",params:{comparison:">=",limit:-90},message:"must be >= -90"}],!1}else return h.errors=[{instancePath:s+"/defaultTilt",schemaPath:"#/properties/defaultTilt/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var p=_===r}else var p=!0;if(p)if(e.defaultViewFov!==void 0){let a=e.defaultViewFov,_=r;if(r===_)if(typeof a=="number"&&isFinite(a)){if(a>120||isNaN(a))return h.errors=[{instancePath:s+"/defaultViewFov",schemaPath:"#/properties/defaultViewFov/maximum",keyword:"maximum",params:{comparison:"<=",limit:120},message:"must be <= 120"}],!1;if(a<20||isNaN(a))return h.errors=[{instancePath:s+"/defaultViewFov",schemaPath:"#/properties/defaultViewFov/minimum",keyword:"minimum",params:{comparison:">=",limit:20},message:"must be >= 20"}],!1}else return h.errors=[{instancePath:s+"/defaultViewFov",schemaPath:"#/properties/defaultViewFov/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var p=_===r}else var p=!0}}}}}}}}}}else return h.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return h.errors=t,r===0}h.evaluated={props:{version:!0,projection:!0,mount:!0,centerX:!0,centerY:!0,radius:!0,fov:!0,rotation:!0,defaultYaw:!0,defaultTilt:!0,defaultViewFov:!0},dynamicProps:!1,dynamicItems:!1};function L(e,{instancePath:s="",parentData:w,parentDataProperty:b,rootData:f=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=L.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(typeof e=="string"){if(v(e)>1023)return L.errors=[{instancePath:s,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:1023},message:"must NOT have more than 1023 characters"}],!1;if(new TextEncoder().encode(e).byteLength>1023)return L.errors=[{instancePath:s,schemaPath:"#/x-lightnvr-max-bytes",keyword:"x-lightnvr-max-bytes",params:{},message:"must fit the UTF-8 byte limit"}],!1;if(e!==""){let y,a=!0;try{y=JSON.parse(e)}catch{a=!1}if(a){let _=r;h(y,{instancePath:s,parentData:e,parentDataProperty:b,rootData:f,dynamicAnchors:d})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var p=_===r;a=p}if(!a)return L.errors=[{instancePath:s,schemaPath:"#/contentSchema",keyword:"contentSchema",params:{},message:"must contain valid JSON matching contentSchema (or be empty)"}],!1}}else return L.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return L.errors=t,r===0}L.evaluated={dynamicProps:!1,dynamicItems:!1};function c(e,{instancePath:s="",parentData:w,parentDataProperty:b,rootData:f=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=c.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),!(typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)))return c.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;if(r===0&&typeof e=="number"&&isFinite(e)){if(e>2147483647||isNaN(e))return c.errors=[{instancePath:s,schemaPath:"#/maximum",keyword:"maximum",params:{comparison:"<=",limit:2147483647},message:"must be <= 2147483647"}],!1;if(e<-2147483648||isNaN(e))return c.errors=[{instancePath:s,schemaPath:"#/minimum",keyword:"minimum",params:{comparison:">=",limit:-2147483648},message:"must be >= -2147483648"}],!1}return c.errors=t,r===0}c.evaluated={dynamicProps:!1,dynamicItems:!1};var H={type:"integer",enum:[0,1],$id:"urn:lightnvr:contract:StreamProtocol"};function I(e,{instancePath:s="",parentData:w,parentDataProperty:b,rootData:f=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=I.evaluated;return o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)?e===0||e===1?(I.errors=t,r===0):(I.errors=[{instancePath:s,schemaPath:"#/enum",keyword:"enum",params:{allowedValues:H.enum},message:"must be equal to one of the allowed values"}],!1):(I.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1)}I.evaluated={dynamicProps:!1,dynamicItems:!1};function N(e,{instancePath:s="",parentData:w,parentDataProperty:b,rootData:f=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=N.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),!(typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)))return N.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;if(r===0&&typeof e=="number"&&isFinite(e)){if(e>365||isNaN(e))return N.errors=[{instancePath:s,schemaPath:"#/maximum",keyword:"maximum",params:{comparison:"<=",limit:365},message:"must be <= 365"}],!1;if(e<-1||isNaN(e))return N.errors=[{instancePath:s,schemaPath:"#/minimum",keyword:"minimum",params:{comparison:">=",limit:-1},message:"must be >= -1"}],!1}return N.errors=t,r===0}N.evaluated={dynamicProps:!1,dynamicItems:!1};function k(e,{instancePath:s="",parentData:w,parentDataProperty:b,rootData:f=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=k.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(Array.isArray(e)){if(e.length>168)return k.errors=[{instancePath:s,schemaPath:"#/maxItems",keyword:"maxItems",params:{limit:168},message:"must NOT have more than 168 items"}],!1;if(e.length<168)return k.errors=[{instancePath:s,schemaPath:"#/minItems",keyword:"minItems",params:{limit:168},message:"must NOT have fewer than 168 items"}],!1;{var p=!0;let y=e.length;for(let a=0;a511)return m.errors=[{instancePath:s+"/url",schemaPath:"#/properties/url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return m.errors=[{instancePath:s+"/url",schemaPath:"#/properties/url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=l===r}else var i=!0;if(i){if(e.enabled!==void 0){let n=r;if(typeof e.enabled!="boolean")return m.errors=[{instancePath:s+"/enabled",schemaPath:"#/properties/enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=n===r}else var i=!0;if(i){if(e.streaming_enabled!==void 0){let n=r;if(typeof e.streaming_enabled!="boolean")return m.errors=[{instancePath:s+"/streaming_enabled",schemaPath:"#/properties/streaming_enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=n===r}else var i=!0;if(i){if(e.playback_transport!==void 0){let n=r;F(e.playback_transport,{instancePath:s+"/playback_transport",parentData:e,parentDataProperty:"playback_transport",rootData:f,dynamicAnchors:d})||(t=t===null?F.errors:t.concat(F.errors),r=t.length);var i=n===r}else var i=!0;if(i){if(e.eptz_config!==void 0){let n=r;L(e.eptz_config,{instancePath:s+"/eptz_config",parentData:e,parentDataProperty:"eptz_config",rootData:f,dynamicAnchors:d})||(t=t===null?L.errors:t.concat(L.errors),r=t.length);var i=n===r}else var i=!0;if(i){if(e.width!==void 0){let n=r;c(e.width,{instancePath:s+"/width",parentData:e,parentDataProperty:"width",rootData:f,dynamicAnchors:d})||(t=t===null?c.errors:t.concat(c.errors),r=t.length);var i=n===r}else var i=!0;if(i){if(e.height!==void 0){let n=r;c(e.height,{instancePath:s+"/height",parentData:e,parentDataProperty:"height",rootData:f,dynamicAnchors:d})||(t=t===null?c.errors:t.concat(c.errors),r=t.length);var i=n===r}else var i=!0;if(i){if(e.fps!==void 0){let n=r;c(e.fps,{instancePath:s+"/fps",parentData:e,parentDataProperty:"fps",rootData:f,dynamicAnchors:d})||(t=t===null?c.errors:t.concat(c.errors),r=t.length);var i=n===r}else var i=!0;if(i){if(e.codec!==void 0){let n=e.codec,l=r;if(r===l)if(typeof n=="string"){if(v(n)>15)return m.errors=[{instancePath:s+"/codec",schemaPath:"#/properties/codec/maxLength",keyword:"maxLength",params:{limit:15},message:"must NOT have more than 15 characters"}],!1}else return m.errors=[{instancePath:s+"/codec",schemaPath:"#/properties/codec/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=l===r}else var i=!0;if(i){if(e.priority!==void 0){let n=r;c(e.priority,{instancePath:s+"/priority",parentData:e,parentDataProperty:"priority",rootData:f,dynamicAnchors:d})||(t=t===null?c.errors:t.concat(c.errors),r=t.length);var i=n===r}else var i=!0;if(i){if(e.record!==void 0){let n=r;if(typeof e.record!="boolean")return m.errors=[{instancePath:s+"/record",schemaPath:"#/properties/record/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=n===r}else var i=!0;if(i){if(e.segment_duration!==void 0){let n=r;c(e.segment_duration,{instancePath:s+"/segment_duration",parentData:e,parentDataProperty:"segment_duration",rootData:f,dynamicAnchors:d})||(t=t===null?c.errors:t.concat(c.errors),r=t.length);var i=n===r}else var i=!0;if(i){if(e.detection_based_recording!==void 0){let n=r;if(typeof e.detection_based_recording!="boolean")return m.errors=[{instancePath:s+"/detection_based_recording",schemaPath:"#/properties/detection_based_recording/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=n===r}else var i=!0;if(i){if(e.detection_model!==void 0){let n=e.detection_model,l=r;if(r===l)if(typeof n=="string"){if(v(n)>511)return m.errors=[{instancePath:s+"/detection_model",schemaPath:"#/properties/detection_model/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return m.errors=[{instancePath:s+"/detection_model",schemaPath:"#/properties/detection_model/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=l===r}else var i=!0;if(i){if(e.detection_threshold!==void 0){let n=e.detection_threshold,l=r;if(!(typeof n=="number"&&!(n%1)&&!isNaN(n)&&isFinite(n)))return m.errors=[{instancePath:s+"/detection_threshold",schemaPath:"#/properties/detection_threshold/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;var i=l===r}else var i=!0;if(i){if(e.detection_interval!==void 0){let n=r;c(e.detection_interval,{instancePath:s+"/detection_interval",parentData:e,parentDataProperty:"detection_interval",rootData:f,dynamicAnchors:d})||(t=t===null?c.errors:t.concat(c.errors),r=t.length);var i=n===r}else var i=!0;if(i){if(e.pre_detection_buffer!==void 0){let n=r;c(e.pre_detection_buffer,{instancePath:s+"/pre_detection_buffer",parentData:e,parentDataProperty:"pre_detection_buffer",rootData:f,dynamicAnchors:d})||(t=t===null?c.errors:t.concat(c.errors),r=t.length);var i=n===r}else var i=!0;if(i){if(e.post_detection_buffer!==void 0){let n=r;c(e.post_detection_buffer,{instancePath:s+"/post_detection_buffer",parentData:e,parentDataProperty:"post_detection_buffer",rootData:f,dynamicAnchors:d})||(t=t===null?c.errors:t.concat(c.errors),r=t.length);var i=n===r}else var i=!0;if(i){if(e.detection_object_filter!==void 0){let n=e.detection_object_filter,l=r;if(r===l)if(typeof n=="string"){if(v(n)>15)return m.errors=[{instancePath:s+"/detection_object_filter",schemaPath:"#/properties/detection_object_filter/maxLength",keyword:"maxLength",params:{limit:15},message:"must NOT have more than 15 characters"}],!1}else return m.errors=[{instancePath:s+"/detection_object_filter",schemaPath:"#/properties/detection_object_filter/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=l===r}else var i=!0;if(i){if(e.detection_object_filter_list!==void 0){let n=e.detection_object_filter_list,l=r;if(r===l)if(typeof n=="string"){if(v(n)>255)return m.errors=[{instancePath:s+"/detection_object_filter_list",schemaPath:"#/properties/detection_object_filter_list/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1}else return m.errors=[{instancePath:s+"/detection_object_filter_list",schemaPath:"#/properties/detection_object_filter_list/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=l===r}else var i=!0;if(i){if(e.protocol!==void 0){let n=r;I(e.protocol,{instancePath:s+"/protocol",parentData:e,parentDataProperty:"protocol",rootData:f,dynamicAnchors:d})||(t=t===null?I.errors:t.concat(I.errors),r=t.length);var i=n===r}else var i=!0;if(i){if(e.record_audio!==void 0){let n=r;if(typeof e.record_audio!="boolean")return m.errors=[{instancePath:s+"/record_audio",schemaPath:"#/properties/record_audio/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=n===r}else var i=!0;if(i){if(e.audio_voice_enhancement!==void 0){let n=r;if(typeof e.audio_voice_enhancement!="boolean")return m.errors=[{instancePath:s+"/audio_voice_enhancement",schemaPath:"#/properties/audio_voice_enhancement/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=n===r}else var i=!0;if(i){if(e.isOnvif!==void 0){let n=r;if(typeof e.isOnvif!="boolean")return m.errors=[{instancePath:s+"/isOnvif",schemaPath:"#/properties/isOnvif/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=n===r}else var i=!0;if(i){if(e.backchannel_enabled!==void 0){let n=r;if(typeof e.backchannel_enabled!="boolean")return m.errors=[{instancePath:s+"/backchannel_enabled",schemaPath:"#/properties/backchannel_enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=n===r}else var i=!0;if(i){if(e.retention_days!==void 0){let n=r;N(e.retention_days,{instancePath:s+"/retention_days",parentData:e,parentDataProperty:"retention_days",rootData:f,dynamicAnchors:d})||(t=t===null?N.errors:t.concat(N.errors),r=t.length);var i=n===r}else var i=!0;if(i){if(e.detection_retention_days!==void 0){let n=r;N(e.detection_retention_days,{instancePath:s+"/detection_retention_days",parentData:e,parentDataProperty:"detection_retention_days",rootData:f,dynamicAnchors:d})||(t=t===null?N.errors:t.concat(N.errors),r=t.length);var i=n===r}else var i=!0;if(i){if(e.max_storage_mb!==void 0){let n=r;c(e.max_storage_mb,{instancePath:s+"/max_storage_mb",parentData:e,parentDataProperty:"max_storage_mb",rootData:f,dynamicAnchors:d})||(t=t===null?c.errors:t.concat(c.errors),r=t.length);var i=n===r}else var i=!0;if(i){if(e.tier_critical_multiplier!==void 0){let n=e.tier_critical_multiplier,l=r;if(!(typeof n=="number"&&isFinite(n)))return m.errors=[{instancePath:s+"/tier_critical_multiplier",schemaPath:"#/properties/tier_critical_multiplier/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var i=l===r}else var i=!0;if(i){if(e.tier_important_multiplier!==void 0){let n=e.tier_important_multiplier,l=r;if(!(typeof n=="number"&&isFinite(n)))return m.errors=[{instancePath:s+"/tier_important_multiplier",schemaPath:"#/properties/tier_important_multiplier/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var i=l===r}else var i=!0;if(i){if(e.tier_ephemeral_multiplier!==void 0){let n=e.tier_ephemeral_multiplier,l=r;if(!(typeof n=="number"&&isFinite(n)))return m.errors=[{instancePath:s+"/tier_ephemeral_multiplier",schemaPath:"#/properties/tier_ephemeral_multiplier/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var i=l===r}else var i=!0;if(i){if(e.storage_priority!==void 0){let n=r;c(e.storage_priority,{instancePath:s+"/storage_priority",parentData:e,parentDataProperty:"storage_priority",rootData:f,dynamicAnchors:d})||(t=t===null?c.errors:t.concat(c.errors),r=t.length);var i=n===r}else var i=!0;if(i){if(e.ptz_enabled!==void 0){let n=r;if(typeof e.ptz_enabled!="boolean")return m.errors=[{instancePath:s+"/ptz_enabled",schemaPath:"#/properties/ptz_enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=n===r}else var i=!0;if(i){if(e.ptz_max_x!==void 0){let n=r;c(e.ptz_max_x,{instancePath:s+"/ptz_max_x",parentData:e,parentDataProperty:"ptz_max_x",rootData:f,dynamicAnchors:d})||(t=t===null?c.errors:t.concat(c.errors),r=t.length);var i=n===r}else var i=!0;if(i){if(e.ptz_max_y!==void 0){let n=r;c(e.ptz_max_y,{instancePath:s+"/ptz_max_y",parentData:e,parentDataProperty:"ptz_max_y",rootData:f,dynamicAnchors:d})||(t=t===null?c.errors:t.concat(c.errors),r=t.length);var i=n===r}else var i=!0;if(i){if(e.ptz_max_z!==void 0){let n=r;c(e.ptz_max_z,{instancePath:s+"/ptz_max_z",parentData:e,parentDataProperty:"ptz_max_z",rootData:f,dynamicAnchors:d})||(t=t===null?c.errors:t.concat(c.errors),r=t.length);var i=n===r}else var i=!0;if(i){if(e.ptz_has_home!==void 0){let n=r;if(typeof e.ptz_has_home!="boolean")return m.errors=[{instancePath:s+"/ptz_has_home",schemaPath:"#/properties/ptz_has_home/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=n===r}else var i=!0;if(i){if(e.onvif_username!==void 0){let n=e.onvif_username,l=r;if(r===l)if(typeof n=="string"){if(v(n)>63)return m.errors=[{instancePath:s+"/onvif_username",schemaPath:"#/properties/onvif_username/maxLength",keyword:"maxLength",params:{limit:63},message:"must NOT have more than 63 characters"}],!1}else return m.errors=[{instancePath:s+"/onvif_username",schemaPath:"#/properties/onvif_username/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=l===r}else var i=!0;if(i){if(e.onvif_password!==void 0){let n=e.onvif_password,l=r;if(r===l)if(typeof n=="string"){if(v(n)>63)return m.errors=[{instancePath:s+"/onvif_password",schemaPath:"#/properties/onvif_password/maxLength",keyword:"maxLength",params:{limit:63},message:"must NOT have more than 63 characters"}],!1}else return m.errors=[{instancePath:s+"/onvif_password",schemaPath:"#/properties/onvif_password/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=l===r}else var i=!0;if(i){if(e.onvif_profile!==void 0){let n=e.onvif_profile,l=r;if(r===l)if(typeof n=="string"){if(v(n)>63)return m.errors=[{instancePath:s+"/onvif_profile",schemaPath:"#/properties/onvif_profile/maxLength",keyword:"maxLength",params:{limit:63},message:"must NOT have more than 63 characters"}],!1}else return m.errors=[{instancePath:s+"/onvif_profile",schemaPath:"#/properties/onvif_profile/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=l===r}else var i=!0;if(i){if(e.onvif_port!==void 0){let n=r;c(e.onvif_port,{instancePath:s+"/onvif_port",parentData:e,parentDataProperty:"onvif_port",rootData:f,dynamicAnchors:d})||(t=t===null?c.errors:t.concat(c.errors),r=t.length);var i=n===r}else var i=!0;if(i){if(e.record_on_schedule!==void 0){let n=r;if(typeof e.record_on_schedule!="boolean")return m.errors=[{instancePath:s+"/record_on_schedule",schemaPath:"#/properties/record_on_schedule/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=n===r}else var i=!0;if(i){if(e.recording_schedule!==void 0){let n=r;k(e.recording_schedule,{instancePath:s+"/recording_schedule",parentData:e,parentDataProperty:"recording_schedule",rootData:f,dynamicAnchors:d})||(t=t===null?k.errors:t.concat(k.errors),r=t.length);var i=n===r}else var i=!0;if(i){if(e.detection_record_on_schedule!==void 0){let n=r;if(typeof e.detection_record_on_schedule!="boolean")return m.errors=[{instancePath:s+"/detection_record_on_schedule",schemaPath:"#/properties/detection_record_on_schedule/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=n===r}else var i=!0;if(i){if(e.detection_recording_schedule!==void 0){let n=r;k(e.detection_recording_schedule,{instancePath:s+"/detection_recording_schedule",parentData:e,parentDataProperty:"detection_recording_schedule",rootData:f,dynamicAnchors:d})||(t=t===null?k.errors:t.concat(k.errors),r=t.length);var i=n===r}else var i=!0;if(i){if(e.tags!==void 0){let n=e.tags,l=r;if(r===l)if(typeof n=="string"){if(v(n)>255)return m.errors=[{instancePath:s+"/tags",schemaPath:"#/properties/tags/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1}else return m.errors=[{instancePath:s+"/tags",schemaPath:"#/properties/tags/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=l===r}else var i=!0;if(i){if(e.admin_url!==void 0){let n=e.admin_url,l=r;if(r===l)if(typeof n=="string"){if(v(n)>511)return m.errors=[{instancePath:s+"/admin_url",schemaPath:"#/properties/admin_url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return m.errors=[{instancePath:s+"/admin_url",schemaPath:"#/properties/admin_url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=l===r}else var i=!0;if(i){if(e.privacy_mode!==void 0){let n=r;if(typeof e.privacy_mode!="boolean")return m.errors=[{instancePath:s+"/privacy_mode",schemaPath:"#/properties/privacy_mode/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=n===r}else var i=!0;if(i){if(e.can_control_privacy!==void 0){let n=r;if(typeof e.can_control_privacy!="boolean")return m.errors=[{instancePath:s+"/can_control_privacy",schemaPath:"#/properties/can_control_privacy/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=n===r}else var i=!0;if(i){if(e.motion_trigger_source!==void 0){let n=e.motion_trigger_source,l=r;if(r===l)if(typeof n=="string"){if(v(n)>255)return m.errors=[{instancePath:s+"/motion_trigger_source",schemaPath:"#/properties/motion_trigger_source/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1}else return m.errors=[{instancePath:s+"/motion_trigger_source",schemaPath:"#/properties/motion_trigger_source/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=l===r}else var i=!0;if(i){if(e.go2rtc_source_override!==void 0){let n=e.go2rtc_source_override,l=r;if(r===l)if(typeof n=="string"){if(v(n)>8191)return m.errors=[{instancePath:s+"/go2rtc_source_override",schemaPath:"#/properties/go2rtc_source_override/maxLength",keyword:"maxLength",params:{limit:8191},message:"must NOT have more than 8191 characters"}],!1}else return m.errors=[{instancePath:s+"/go2rtc_source_override",schemaPath:"#/properties/go2rtc_source_override/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=l===r}else var i=!0;if(i){if(e.sub_stream_url!==void 0){let n=e.sub_stream_url,l=r;if(r===l)if(typeof n=="string"){if(v(n)>511)return m.errors=[{instancePath:s+"/sub_stream_url",schemaPath:"#/properties/sub_stream_url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return m.errors=[{instancePath:s+"/sub_stream_url",schemaPath:"#/properties/sub_stream_url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=l===r}else var i=!0;if(i){if(e.has_sub_stream!==void 0){let n=r;if(typeof e.has_sub_stream!="boolean")return m.errors=[{instancePath:s+"/has_sub_stream",schemaPath:"#/properties/has_sub_stream/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=n===r}else var i=!0;if(i){if(e.detection_url!==void 0){let n=e.detection_url,l=r;if(r===l)if(typeof n=="string"){if(v(n)>511)return m.errors=[{instancePath:s+"/detection_url",schemaPath:"#/properties/detection_url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return m.errors=[{instancePath:s+"/detection_url",schemaPath:"#/properties/detection_url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=l===r}else var i=!0;if(i){if(e.publish_url!==void 0){let n=e.publish_url,l=r;if(r===l)if(typeof n=="string"){if(v(n)>511)return m.errors=[{instancePath:s+"/publish_url",schemaPath:"#/properties/publish_url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return m.errors=[{instancePath:s+"/publish_url",schemaPath:"#/properties/publish_url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=l===r}else var i=!0;if(i){if(e.status!==void 0){let n=r;$(e.status,{instancePath:s+"/status",parentData:e,parentDataProperty:"status",rootData:f,dynamicAnchors:d})||(t=t===null?$.errors:t.concat($.errors),r=t.length);var i=n===r}else var i=!0;if(i){if(e.error_message!==void 0){let n=e.error_message,l=r;if(r===l)if(typeof n=="string"){if(v(n)>255)return m.errors=[{instancePath:s+"/error_message",schemaPath:"#/properties/error_message/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1;if(v(n)<1)return m.errors=[{instancePath:s+"/error_message",schemaPath:"#/properties/error_message/minLength",keyword:"minLength",params:{limit:1},message:"must NOT have fewer than 1 characters"}],!1}else return m.errors=[{instancePath:s+"/error_message",schemaPath:"#/properties/error_message/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=l===r}else var i=!0;if(i)if(e.go2rtc_hls_available!==void 0){let n=r;if(typeof e.go2rtc_hls_available!="boolean")return m.errors=[{instancePath:s+"/go2rtc_hls_available",schemaPath:"#/properties/go2rtc_hls_available/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=n===r}else var i=!0}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}else return m.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return m.errors=t,r===0}m.evaluated={props:{camera_uuid:!0,location_uuid:!0,name:!0,url:!0,enabled:!0,streaming_enabled:!0,playback_transport:!0,eptz_config:!0,width:!0,height:!0,fps:!0,codec:!0,priority:!0,record:!0,segment_duration:!0,detection_based_recording:!0,detection_model:!0,detection_threshold:!0,detection_interval:!0,pre_detection_buffer:!0,post_detection_buffer:!0,detection_object_filter:!0,detection_object_filter_list:!0,protocol:!0,record_audio:!0,audio_voice_enhancement:!0,isOnvif:!0,backchannel_enabled:!0,retention_days:!0,detection_retention_days:!0,max_storage_mb:!0,tier_critical_multiplier:!0,tier_important_multiplier:!0,tier_ephemeral_multiplier:!0,storage_priority:!0,ptz_enabled:!0,ptz_max_x:!0,ptz_max_y:!0,ptz_max_z:!0,ptz_has_home:!0,onvif_username:!0,onvif_password:!0,onvif_profile:!0,onvif_port:!0,record_on_schedule:!0,recording_schedule:!0,detection_record_on_schedule:!0,detection_recording_schedule:!0,tags:!0,admin_url:!0,privacy_mode:!0,can_control_privacy:!0,motion_trigger_source:!0,go2rtc_source_override:!0,sub_stream_url:!0,has_sub_stream:!0,detection_url:!0,publish_url:!0,status:!0,error_message:!0,go2rtc_hls_available:!0},dynamicProps:!1,dynamicItems:!1};var J=new RegExp("^(?![A-Za-z][A-Za-z0-9+.-]*://[^/?#]*@)","u");function O(e,{instancePath:s="",parentData:w,parentDataProperty:b,rootData:f=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=O.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(typeof e=="string"){if(v(e)>511)return O.errors=[{instancePath:s,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1;if(!J.test(e))return O.errors=[{instancePath:s,schemaPath:"#/pattern",keyword:"pattern",params:{pattern:"^(?![A-Za-z][A-Za-z0-9+.-]*://[^/?#]*@)"},message:'must match pattern "^(?![A-Za-z][A-Za-z0-9+.-]*://[^/?#]*@)"'}],!1}else return O.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return O.errors=t,r===0}O.evaluated={dynamicProps:!1,dynamicItems:!1};function j(e,{instancePath:s="",parentData:w,parentDataProperty:b,rootData:f=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=j.evaluated;o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0);let p=r;m(e,{instancePath:s,parentData:w,parentDataProperty:b,rootData:f,dynamicAnchors:d})||(t=t===null?m.errors:t.concat(m.errors),r=t.length);var y=p===r;if(y){let S=r,i=r,u=!0,n=r,l=r,x=!1,Y=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let g;if(e.isOnvif===void 0&&(g="isOnvif")){let P={};t===null?t=[P]:t.push(P),r++}else if(e.isOnvif!==void 0&&e.isOnvif!==!0){let P={};t===null?t=[P]:t.push(P),r++}}var _=Y===r;if(x=x||_,_){var a={};a.isOnvif=!0}let E=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let g;if(e.onvif_username===void 0&&(g="onvif_username")){let P={};t===null?t=[P]:t.push(P),r++}else if(e.onvif_username!==void 0){let P=e.onvif_username;if(typeof P=="string"&&v(P)<1){let q={};t===null?t=[q]:t.push(q),r++}}}var _=E===r;x=x||_,_&&a!==!0&&(a=a||{},a.onvif_username=!0);let R=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let g;if(e.onvif_password===void 0&&(g="onvif_password")){let P={};t===null?t=[P]:t.push(P),r++}else if(e.onvif_password!==void 0){let P=e.onvif_password;if(typeof P=="string"&&v(P)<1){let q={};t===null?t=[q]:t.push(q),r++}}}var _=R===r;if(x=x||_,_&&a!==!0&&(a=a||{},a.onvif_password=!0),x)r=l,t!==null&&(l?t.length=l:t=null);else{let g={};t===null?t=[g]:t.push(g),r++}var T=n===r;if(r=i,t!==null&&(i?t.length=i:t=null),T){let g=r;e&&typeof e=="object"&&!Array.isArray(e)&&e.url!==void 0&&(O(e.url,{instancePath:s+"/url",parentData:e,parentDataProperty:"url",rootData:f,dynamicAnchors:d})||(t=t===null?O.errors:t.concat(O.errors),r=t.length));var T=g===r;u=T,u&&a!==!0&&(a=a||{},a.url=!0)}if(!u){let g={instancePath:s,schemaPath:"#/allOf/1/if",keyword:"if",params:{failingKeyword:"then"},message:'must match "then" schema'};return t===null?t=[g]:t.push(g),r++,j.errors=t,!1}if(r===S)if(e&&typeof e=="object"&&!Array.isArray(e)){let g;if(e.url===void 0&&(g="url")||e.onvif_username===void 0&&(g="onvif_username")||e.onvif_password===void 0&&(g="onvif_password")||e.admin_url===void 0&&(g="admin_url")||e.go2rtc_source_override===void 0&&(g="go2rtc_source_override")||e.sub_stream_url===void 0&&(g="sub_stream_url")||e.detection_url===void 0&&(g="detection_url")||e.publish_url===void 0&&(g="publish_url"))return j.errors=[{instancePath:s,schemaPath:"#/allOf/1/required",keyword:"required",params:{missingProperty:g},message:"must have required property '"+g+"'"}],!1;if(a!==!0&&(a=a||{},a.can_control_privacy=!0),e.can_control_privacy!==void 0&&e.can_control_privacy!==!0)return j.errors=[{instancePath:s+"/can_control_privacy",schemaPath:"#/allOf/1/properties/can_control_privacy/const",keyword:"const",params:{allowedValue:!0},message:"must be equal to constant"}],!1}else return j.errors=[{instancePath:s,schemaPath:"#/allOf/1/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;var y=S===r;y&&a!==!0&&(a=a||{},a.camera_uuid=!0,a.location_uuid=!0,a.name=!0,a.url=!0,a.enabled=!0,a.streaming_enabled=!0,a.playback_transport=!0,a.eptz_config=!0,a.width=!0,a.height=!0,a.fps=!0,a.codec=!0,a.priority=!0,a.record=!0,a.segment_duration=!0,a.detection_based_recording=!0,a.detection_model=!0,a.detection_threshold=!0,a.detection_interval=!0,a.pre_detection_buffer=!0,a.post_detection_buffer=!0,a.detection_object_filter=!0,a.detection_object_filter_list=!0,a.protocol=!0,a.record_audio=!0,a.audio_voice_enhancement=!0,a.isOnvif=!0,a.backchannel_enabled=!0,a.retention_days=!0,a.detection_retention_days=!0,a.max_storage_mb=!0,a.tier_critical_multiplier=!0,a.tier_important_multiplier=!0,a.tier_ephemeral_multiplier=!0,a.storage_priority=!0,a.ptz_enabled=!0,a.ptz_max_x=!0,a.ptz_max_y=!0,a.ptz_max_z=!0,a.ptz_has_home=!0,a.onvif_username=!0,a.onvif_password=!0,a.onvif_profile=!0,a.onvif_port=!0,a.record_on_schedule=!0,a.recording_schedule=!0,a.detection_record_on_schedule=!0,a.detection_recording_schedule=!0,a.tags=!0,a.admin_url=!0,a.privacy_mode=!0,a.can_control_privacy=!0,a.motion_trigger_source=!0,a.go2rtc_source_override=!0,a.sub_stream_url=!0,a.has_sub_stream=!0,a.detection_url=!0,a.publish_url=!0,a.status=!0,a.error_message=!0,a.go2rtc_hls_available=!0)}return j.errors=t,o.props=a,r===0}j.evaluated={dynamicProps:!0,dynamicItems:!1};function A(e,{instancePath:s="",parentData:w,parentDataProperty:b,rootData:f=e,dynamicAnchors:d={}}={}){let t=null,r=0,o=A.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(e&&typeof e=="object"&&!Array.isArray(e)){let p;if(e.stream===void 0&&(p="stream"))return A.errors=[{instancePath:s,schemaPath:"#/required",keyword:"required",params:{missingProperty:p},message:"must have required property '"+p+"'"}],!1;e.stream!==void 0&&(j(e.stream,{instancePath:s+"/stream",parentData:e,parentDataProperty:"stream",rootData:f,dynamicAnchors:d})||(t=t===null?j.errors:t.concat(j.errors),r=t.length))}else return A.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return A.errors=t,r===0}A.evaluated={props:{stream:!0},dynamicProps:!1,dynamicItems:!1};export{G as validateStreamFullResponseConfigurable}; diff --git a/web/js/api/generated/validators/StreamFullResponseLiveOnly.d.mts b/web/js/api/generated/validators/StreamFullResponseLiveOnly.d.mts new file mode 100644 index 000000000..d821d1dba --- /dev/null +++ b/web/js/api/generated/validators/StreamFullResponseLiveOnly.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateStreamFullResponseLiveOnly } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/StreamFullResponseLiveOnly.mjs b/web/js/api/generated/validators/StreamFullResponseLiveOnly.mjs new file mode 100644 index 000000000..51d19b6b3 --- /dev/null +++ b/web/js/api/generated/validators/StreamFullResponseLiveOnly.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var X=(e,s)=>()=>{try{return s||e((s={exports:{}}).exports,s),s.exports}catch(b){throw s=0,b}};var U=X(q=>{"use strict";Object.defineProperty(q,"__esModule",{value:!0});function V(e){let s=e.length,b=0,v=0,p;for(;v=55296&&p<=56319&&v36)return x.errors=[{instancePath:s,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:36},message:"must NOT have more than 36 characters"}],!1;if(!C.test(e))return x.errors=[{instancePath:s,schemaPath:"#/pattern",keyword:"pattern",params:{pattern:"^(|[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})$"},message:'must match pattern "^(|[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})$"'}],!1}else return x.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return x.errors=t,r===0}x.evaluated={dynamicProps:!1,dynamicItems:!1};function D(e,{instancePath:s="",parentData:b,parentDataProperty:v,rootData:p=e,dynamicAnchors:f={}}={}){let t=null,r=0,o=D.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(typeof e=="string"){if(w(e)>255)return D.errors=[{instancePath:s,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1;if(w(e)<1)return D.errors=[{instancePath:s,schemaPath:"#/minLength",keyword:"minLength",params:{limit:1},message:"must NOT have fewer than 1 characters"}],!1;if(new TextEncoder().encode(e).byteLength>255)return D.errors=[{instancePath:s,schemaPath:"#/x-lightnvr-max-bytes",keyword:"x-lightnvr-max-bytes",params:{},message:"must fit the UTF-8 byte limit"}],!1}else return D.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return D.errors=t,r===0}D.evaluated={dynamicProps:!1,dynamicItems:!1};var W={type:"string",enum:["auto","webrtc_only","mse_only","hls_only","webrtc_then_mse","mse_then_hls"],$id:"urn:lightnvr:contract:PlaybackTransport"};function $(e,{instancePath:s="",parentData:b,parentDataProperty:v,rootData:p=e,dynamicAnchors:f={}}={}){let t=null,r=0,o=$.evaluated;return o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e!="string"?($.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1):e==="auto"||e==="webrtc_only"||e==="mse_only"||e==="hls_only"||e==="webrtc_then_mse"||e==="mse_then_hls"?($.errors=t,r===0):($.errors=[{instancePath:s,schemaPath:"#/enum",keyword:"enum",params:{allowedValues:W.enum},message:"must be equal to one of the allowed values"}],!1)}$.evaluated={dynamicProps:!1,dynamicItems:!1};function h(e,{instancePath:s="",parentData:b,parentDataProperty:v,rootData:p=e,dynamicAnchors:f={}}={}){let t=null,r=0,o=h.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(e&&typeof e=="object"&&!Array.isArray(e)){let y;if(e.version===void 0&&(y="version")||e.projection===void 0&&(y="projection")||e.mount===void 0&&(y="mount")||e.centerX===void 0&&(y="centerX")||e.centerY===void 0&&(y="centerY")||e.radius===void 0&&(y="radius")||e.fov===void 0&&(y="fov")||e.rotation===void 0&&(y="rotation")||e.defaultYaw===void 0&&(y="defaultYaw")||e.defaultTilt===void 0&&(y="defaultTilt")||e.defaultViewFov===void 0&&(y="defaultViewFov"))return h.errors=[{instancePath:s,schemaPath:"#/required",keyword:"required",params:{missingProperty:y},message:"must have required property '"+y+"'"}],!1;if(e.version!==void 0){let a=r;if(e.version!==1)return h.errors=[{instancePath:s+"/version",schemaPath:"#/properties/version/const",keyword:"const",params:{allowedValue:1},message:"must be equal to constant"}],!1;var d=a===r}else var d=!0;if(d){if(e.projection!==void 0){let a=r;if(e.projection!=="equidistant")return h.errors=[{instancePath:s+"/projection",schemaPath:"#/properties/projection/const",keyword:"const",params:{allowedValue:"equidistant"},message:"must be equal to constant"}],!1;var d=a===r}else var d=!0;if(d){if(e.mount!==void 0){let a=r;if(e.mount!=="ceiling")return h.errors=[{instancePath:s+"/mount",schemaPath:"#/properties/mount/const",keyword:"const",params:{allowedValue:"ceiling"},message:"must be equal to constant"}],!1;var d=a===r}else var d=!0;if(d){if(e.centerX!==void 0){let a=e.centerX,c=r;if(r===c)if(typeof a=="number"&&isFinite(a)){if(a>1||isNaN(a))return h.errors=[{instancePath:s+"/centerX",schemaPath:"#/properties/centerX/maximum",keyword:"maximum",params:{comparison:"<=",limit:1},message:"must be <= 1"}],!1;if(a<0||isNaN(a))return h.errors=[{instancePath:s+"/centerX",schemaPath:"#/properties/centerX/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1}else return h.errors=[{instancePath:s+"/centerX",schemaPath:"#/properties/centerX/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var d=c===r}else var d=!0;if(d){if(e.centerY!==void 0){let a=e.centerY,c=r;if(r===c)if(typeof a=="number"&&isFinite(a)){if(a>1||isNaN(a))return h.errors=[{instancePath:s+"/centerY",schemaPath:"#/properties/centerY/maximum",keyword:"maximum",params:{comparison:"<=",limit:1},message:"must be <= 1"}],!1;if(a<0||isNaN(a))return h.errors=[{instancePath:s+"/centerY",schemaPath:"#/properties/centerY/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1}else return h.errors=[{instancePath:s+"/centerY",schemaPath:"#/properties/centerY/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var d=c===r}else var d=!0;if(d){if(e.radius!==void 0){let a=e.radius,c=r;if(r===c)if(typeof a=="number"&&isFinite(a)){if(a>1||isNaN(a))return h.errors=[{instancePath:s+"/radius",schemaPath:"#/properties/radius/maximum",keyword:"maximum",params:{comparison:"<=",limit:1},message:"must be <= 1"}],!1;if(a<.05||isNaN(a))return h.errors=[{instancePath:s+"/radius",schemaPath:"#/properties/radius/minimum",keyword:"minimum",params:{comparison:">=",limit:.05},message:"must be >= 0.05"}],!1}else return h.errors=[{instancePath:s+"/radius",schemaPath:"#/properties/radius/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var d=c===r}else var d=!0;if(d){if(e.fov!==void 0){let a=e.fov,c=r;if(r===c)if(typeof a=="number"&&isFinite(a)){if(a>360||isNaN(a))return h.errors=[{instancePath:s+"/fov",schemaPath:"#/properties/fov/maximum",keyword:"maximum",params:{comparison:"<=",limit:360},message:"must be <= 360"}],!1;if(a<160||isNaN(a))return h.errors=[{instancePath:s+"/fov",schemaPath:"#/properties/fov/minimum",keyword:"minimum",params:{comparison:">=",limit:160},message:"must be >= 160"}],!1}else return h.errors=[{instancePath:s+"/fov",schemaPath:"#/properties/fov/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var d=c===r}else var d=!0;if(d){if(e.rotation!==void 0){let a=e.rotation,c=r;if(r===c)if(typeof a=="number"&&isFinite(a)){if(a>360||isNaN(a))return h.errors=[{instancePath:s+"/rotation",schemaPath:"#/properties/rotation/maximum",keyword:"maximum",params:{comparison:"<=",limit:360},message:"must be <= 360"}],!1;if(a<-360||isNaN(a))return h.errors=[{instancePath:s+"/rotation",schemaPath:"#/properties/rotation/minimum",keyword:"minimum",params:{comparison:">=",limit:-360},message:"must be >= -360"}],!1}else return h.errors=[{instancePath:s+"/rotation",schemaPath:"#/properties/rotation/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var d=c===r}else var d=!0;if(d){if(e.defaultYaw!==void 0){let a=e.defaultYaw,c=r;if(r===c)if(typeof a=="number"&&isFinite(a)){if(a>360||isNaN(a))return h.errors=[{instancePath:s+"/defaultYaw",schemaPath:"#/properties/defaultYaw/maximum",keyword:"maximum",params:{comparison:"<=",limit:360},message:"must be <= 360"}],!1;if(a<-360||isNaN(a))return h.errors=[{instancePath:s+"/defaultYaw",schemaPath:"#/properties/defaultYaw/minimum",keyword:"minimum",params:{comparison:">=",limit:-360},message:"must be >= -360"}],!1}else return h.errors=[{instancePath:s+"/defaultYaw",schemaPath:"#/properties/defaultYaw/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var d=c===r}else var d=!0;if(d){if(e.defaultTilt!==void 0){let a=e.defaultTilt,c=r;if(r===c)if(typeof a=="number"&&isFinite(a)){if(a>30||isNaN(a))return h.errors=[{instancePath:s+"/defaultTilt",schemaPath:"#/properties/defaultTilt/maximum",keyword:"maximum",params:{comparison:"<=",limit:30},message:"must be <= 30"}],!1;if(a<-90||isNaN(a))return h.errors=[{instancePath:s+"/defaultTilt",schemaPath:"#/properties/defaultTilt/minimum",keyword:"minimum",params:{comparison:">=",limit:-90},message:"must be >= -90"}],!1}else return h.errors=[{instancePath:s+"/defaultTilt",schemaPath:"#/properties/defaultTilt/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var d=c===r}else var d=!0;if(d)if(e.defaultViewFov!==void 0){let a=e.defaultViewFov,c=r;if(r===c)if(typeof a=="number"&&isFinite(a)){if(a>120||isNaN(a))return h.errors=[{instancePath:s+"/defaultViewFov",schemaPath:"#/properties/defaultViewFov/maximum",keyword:"maximum",params:{comparison:"<=",limit:120},message:"must be <= 120"}],!1;if(a<20||isNaN(a))return h.errors=[{instancePath:s+"/defaultViewFov",schemaPath:"#/properties/defaultViewFov/minimum",keyword:"minimum",params:{comparison:">=",limit:20},message:"must be >= 20"}],!1}else return h.errors=[{instancePath:s+"/defaultViewFov",schemaPath:"#/properties/defaultViewFov/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var d=c===r}else var d=!0}}}}}}}}}}else return h.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return h.errors=t,r===0}h.evaluated={props:{version:!0,projection:!0,mount:!0,centerX:!0,centerY:!0,radius:!0,fov:!0,rotation:!0,defaultYaw:!0,defaultTilt:!0,defaultViewFov:!0},dynamicProps:!1,dynamicItems:!1};function z(e,{instancePath:s="",parentData:b,parentDataProperty:v,rootData:p=e,dynamicAnchors:f={}}={}){let t=null,r=0,o=z.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(typeof e=="string"){if(w(e)>1023)return z.errors=[{instancePath:s,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:1023},message:"must NOT have more than 1023 characters"}],!1;if(new TextEncoder().encode(e).byteLength>1023)return z.errors=[{instancePath:s,schemaPath:"#/x-lightnvr-max-bytes",keyword:"x-lightnvr-max-bytes",params:{},message:"must fit the UTF-8 byte limit"}],!1;if(e!==""){let y,a=!0;try{y=JSON.parse(e)}catch{a=!1}if(a){let c=r;h(y,{instancePath:s,parentData:e,parentDataProperty:v,rootData:p,dynamicAnchors:f})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var d=c===r;a=d}if(!a)return z.errors=[{instancePath:s,schemaPath:"#/contentSchema",keyword:"contentSchema",params:{},message:"must contain valid JSON matching contentSchema (or be empty)"}],!1}}else return z.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return z.errors=t,r===0}z.evaluated={dynamicProps:!1,dynamicItems:!1};function _(e,{instancePath:s="",parentData:b,parentDataProperty:v,rootData:p=e,dynamicAnchors:f={}}={}){let t=null,r=0,o=_.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),!(typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)))return _.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;if(r===0&&typeof e=="number"&&isFinite(e)){if(e>2147483647||isNaN(e))return _.errors=[{instancePath:s,schemaPath:"#/maximum",keyword:"maximum",params:{comparison:"<=",limit:2147483647},message:"must be <= 2147483647"}],!1;if(e<-2147483648||isNaN(e))return _.errors=[{instancePath:s,schemaPath:"#/minimum",keyword:"minimum",params:{comparison:">=",limit:-2147483648},message:"must be >= -2147483648"}],!1}return _.errors=t,r===0}_.evaluated={dynamicProps:!1,dynamicItems:!1};var Z={type:"integer",enum:[0,1],$id:"urn:lightnvr:contract:StreamProtocol"};function I(e,{instancePath:s="",parentData:b,parentDataProperty:v,rootData:p=e,dynamicAnchors:f={}}={}){let t=null,r=0,o=I.evaluated;return o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)?e===0||e===1?(I.errors=t,r===0):(I.errors=[{instancePath:s,schemaPath:"#/enum",keyword:"enum",params:{allowedValues:Z.enum},message:"must be equal to one of the allowed values"}],!1):(I.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1)}I.evaluated={dynamicProps:!1,dynamicItems:!1};function k(e,{instancePath:s="",parentData:b,parentDataProperty:v,rootData:p=e,dynamicAnchors:f={}}={}){let t=null,r=0,o=k.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),!(typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)))return k.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;if(r===0&&typeof e=="number"&&isFinite(e)){if(e>365||isNaN(e))return k.errors=[{instancePath:s,schemaPath:"#/maximum",keyword:"maximum",params:{comparison:"<=",limit:365},message:"must be <= 365"}],!1;if(e<-1||isNaN(e))return k.errors=[{instancePath:s,schemaPath:"#/minimum",keyword:"minimum",params:{comparison:">=",limit:-1},message:"must be >= -1"}],!1}return k.errors=t,r===0}k.evaluated={dynamicProps:!1,dynamicItems:!1};function P(e,{instancePath:s="",parentData:b,parentDataProperty:v,rootData:p=e,dynamicAnchors:f={}}={}){let t=null,r=0,o=P.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(Array.isArray(e)){if(e.length>168)return P.errors=[{instancePath:s,schemaPath:"#/maxItems",keyword:"maxItems",params:{limit:168},message:"must NOT have more than 168 items"}],!1;if(e.length<168)return P.errors=[{instancePath:s,schemaPath:"#/minItems",keyword:"minItems",params:{limit:168},message:"must NOT have fewer than 168 items"}],!1;{var d=!0;let y=e.length;for(let a=0;a511)return l.errors=[{instancePath:s+"/url",schemaPath:"#/properties/url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return l.errors=[{instancePath:s+"/url",schemaPath:"#/properties/url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=u===r}else var n=!0;if(n){if(e.enabled!==void 0){let i=r;if(typeof e.enabled!="boolean")return l.errors=[{instancePath:s+"/enabled",schemaPath:"#/properties/enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=i===r}else var n=!0;if(n){if(e.streaming_enabled!==void 0){let i=r;if(typeof e.streaming_enabled!="boolean")return l.errors=[{instancePath:s+"/streaming_enabled",schemaPath:"#/properties/streaming_enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=i===r}else var n=!0;if(n){if(e.playback_transport!==void 0){let i=r;$(e.playback_transport,{instancePath:s+"/playback_transport",parentData:e,parentDataProperty:"playback_transport",rootData:p,dynamicAnchors:f})||(t=t===null?$.errors:t.concat($.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.eptz_config!==void 0){let i=r;z(e.eptz_config,{instancePath:s+"/eptz_config",parentData:e,parentDataProperty:"eptz_config",rootData:p,dynamicAnchors:f})||(t=t===null?z.errors:t.concat(z.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.width!==void 0){let i=r;_(e.width,{instancePath:s+"/width",parentData:e,parentDataProperty:"width",rootData:p,dynamicAnchors:f})||(t=t===null?_.errors:t.concat(_.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.height!==void 0){let i=r;_(e.height,{instancePath:s+"/height",parentData:e,parentDataProperty:"height",rootData:p,dynamicAnchors:f})||(t=t===null?_.errors:t.concat(_.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.fps!==void 0){let i=r;_(e.fps,{instancePath:s+"/fps",parentData:e,parentDataProperty:"fps",rootData:p,dynamicAnchors:f})||(t=t===null?_.errors:t.concat(_.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.codec!==void 0){let i=e.codec,u=r;if(r===u)if(typeof i=="string"){if(w(i)>15)return l.errors=[{instancePath:s+"/codec",schemaPath:"#/properties/codec/maxLength",keyword:"maxLength",params:{limit:15},message:"must NOT have more than 15 characters"}],!1}else return l.errors=[{instancePath:s+"/codec",schemaPath:"#/properties/codec/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=u===r}else var n=!0;if(n){if(e.priority!==void 0){let i=r;_(e.priority,{instancePath:s+"/priority",parentData:e,parentDataProperty:"priority",rootData:p,dynamicAnchors:f})||(t=t===null?_.errors:t.concat(_.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.record!==void 0){let i=r;if(typeof e.record!="boolean")return l.errors=[{instancePath:s+"/record",schemaPath:"#/properties/record/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=i===r}else var n=!0;if(n){if(e.segment_duration!==void 0){let i=r;_(e.segment_duration,{instancePath:s+"/segment_duration",parentData:e,parentDataProperty:"segment_duration",rootData:p,dynamicAnchors:f})||(t=t===null?_.errors:t.concat(_.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.detection_based_recording!==void 0){let i=r;if(typeof e.detection_based_recording!="boolean")return l.errors=[{instancePath:s+"/detection_based_recording",schemaPath:"#/properties/detection_based_recording/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=i===r}else var n=!0;if(n){if(e.detection_model!==void 0){let i=e.detection_model,u=r;if(r===u)if(typeof i=="string"){if(w(i)>511)return l.errors=[{instancePath:s+"/detection_model",schemaPath:"#/properties/detection_model/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return l.errors=[{instancePath:s+"/detection_model",schemaPath:"#/properties/detection_model/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=u===r}else var n=!0;if(n){if(e.detection_threshold!==void 0){let i=e.detection_threshold,u=r;if(!(typeof i=="number"&&!(i%1)&&!isNaN(i)&&isFinite(i)))return l.errors=[{instancePath:s+"/detection_threshold",schemaPath:"#/properties/detection_threshold/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;var n=u===r}else var n=!0;if(n){if(e.detection_interval!==void 0){let i=r;_(e.detection_interval,{instancePath:s+"/detection_interval",parentData:e,parentDataProperty:"detection_interval",rootData:p,dynamicAnchors:f})||(t=t===null?_.errors:t.concat(_.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.pre_detection_buffer!==void 0){let i=r;_(e.pre_detection_buffer,{instancePath:s+"/pre_detection_buffer",parentData:e,parentDataProperty:"pre_detection_buffer",rootData:p,dynamicAnchors:f})||(t=t===null?_.errors:t.concat(_.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.post_detection_buffer!==void 0){let i=r;_(e.post_detection_buffer,{instancePath:s+"/post_detection_buffer",parentData:e,parentDataProperty:"post_detection_buffer",rootData:p,dynamicAnchors:f})||(t=t===null?_.errors:t.concat(_.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.detection_object_filter!==void 0){let i=e.detection_object_filter,u=r;if(r===u)if(typeof i=="string"){if(w(i)>15)return l.errors=[{instancePath:s+"/detection_object_filter",schemaPath:"#/properties/detection_object_filter/maxLength",keyword:"maxLength",params:{limit:15},message:"must NOT have more than 15 characters"}],!1}else return l.errors=[{instancePath:s+"/detection_object_filter",schemaPath:"#/properties/detection_object_filter/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=u===r}else var n=!0;if(n){if(e.detection_object_filter_list!==void 0){let i=e.detection_object_filter_list,u=r;if(r===u)if(typeof i=="string"){if(w(i)>255)return l.errors=[{instancePath:s+"/detection_object_filter_list",schemaPath:"#/properties/detection_object_filter_list/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1}else return l.errors=[{instancePath:s+"/detection_object_filter_list",schemaPath:"#/properties/detection_object_filter_list/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=u===r}else var n=!0;if(n){if(e.protocol!==void 0){let i=r;I(e.protocol,{instancePath:s+"/protocol",parentData:e,parentDataProperty:"protocol",rootData:p,dynamicAnchors:f})||(t=t===null?I.errors:t.concat(I.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.record_audio!==void 0){let i=r;if(typeof e.record_audio!="boolean")return l.errors=[{instancePath:s+"/record_audio",schemaPath:"#/properties/record_audio/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=i===r}else var n=!0;if(n){if(e.audio_voice_enhancement!==void 0){let i=r;if(typeof e.audio_voice_enhancement!="boolean")return l.errors=[{instancePath:s+"/audio_voice_enhancement",schemaPath:"#/properties/audio_voice_enhancement/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=i===r}else var n=!0;if(n){if(e.isOnvif!==void 0){let i=r;if(typeof e.isOnvif!="boolean")return l.errors=[{instancePath:s+"/isOnvif",schemaPath:"#/properties/isOnvif/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=i===r}else var n=!0;if(n){if(e.backchannel_enabled!==void 0){let i=r;if(typeof e.backchannel_enabled!="boolean")return l.errors=[{instancePath:s+"/backchannel_enabled",schemaPath:"#/properties/backchannel_enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=i===r}else var n=!0;if(n){if(e.retention_days!==void 0){let i=r;k(e.retention_days,{instancePath:s+"/retention_days",parentData:e,parentDataProperty:"retention_days",rootData:p,dynamicAnchors:f})||(t=t===null?k.errors:t.concat(k.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.detection_retention_days!==void 0){let i=r;k(e.detection_retention_days,{instancePath:s+"/detection_retention_days",parentData:e,parentDataProperty:"detection_retention_days",rootData:p,dynamicAnchors:f})||(t=t===null?k.errors:t.concat(k.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.max_storage_mb!==void 0){let i=r;_(e.max_storage_mb,{instancePath:s+"/max_storage_mb",parentData:e,parentDataProperty:"max_storage_mb",rootData:p,dynamicAnchors:f})||(t=t===null?_.errors:t.concat(_.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.tier_critical_multiplier!==void 0){let i=e.tier_critical_multiplier,u=r;if(!(typeof i=="number"&&isFinite(i)))return l.errors=[{instancePath:s+"/tier_critical_multiplier",schemaPath:"#/properties/tier_critical_multiplier/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var n=u===r}else var n=!0;if(n){if(e.tier_important_multiplier!==void 0){let i=e.tier_important_multiplier,u=r;if(!(typeof i=="number"&&isFinite(i)))return l.errors=[{instancePath:s+"/tier_important_multiplier",schemaPath:"#/properties/tier_important_multiplier/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var n=u===r}else var n=!0;if(n){if(e.tier_ephemeral_multiplier!==void 0){let i=e.tier_ephemeral_multiplier,u=r;if(!(typeof i=="number"&&isFinite(i)))return l.errors=[{instancePath:s+"/tier_ephemeral_multiplier",schemaPath:"#/properties/tier_ephemeral_multiplier/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var n=u===r}else var n=!0;if(n){if(e.storage_priority!==void 0){let i=r;_(e.storage_priority,{instancePath:s+"/storage_priority",parentData:e,parentDataProperty:"storage_priority",rootData:p,dynamicAnchors:f})||(t=t===null?_.errors:t.concat(_.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.ptz_enabled!==void 0){let i=r;if(typeof e.ptz_enabled!="boolean")return l.errors=[{instancePath:s+"/ptz_enabled",schemaPath:"#/properties/ptz_enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=i===r}else var n=!0;if(n){if(e.ptz_max_x!==void 0){let i=r;_(e.ptz_max_x,{instancePath:s+"/ptz_max_x",parentData:e,parentDataProperty:"ptz_max_x",rootData:p,dynamicAnchors:f})||(t=t===null?_.errors:t.concat(_.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.ptz_max_y!==void 0){let i=r;_(e.ptz_max_y,{instancePath:s+"/ptz_max_y",parentData:e,parentDataProperty:"ptz_max_y",rootData:p,dynamicAnchors:f})||(t=t===null?_.errors:t.concat(_.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.ptz_max_z!==void 0){let i=r;_(e.ptz_max_z,{instancePath:s+"/ptz_max_z",parentData:e,parentDataProperty:"ptz_max_z",rootData:p,dynamicAnchors:f})||(t=t===null?_.errors:t.concat(_.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.ptz_has_home!==void 0){let i=r;if(typeof e.ptz_has_home!="boolean")return l.errors=[{instancePath:s+"/ptz_has_home",schemaPath:"#/properties/ptz_has_home/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=i===r}else var n=!0;if(n){if(e.onvif_username!==void 0){let i=e.onvif_username,u=r;if(r===u)if(typeof i=="string"){if(w(i)>63)return l.errors=[{instancePath:s+"/onvif_username",schemaPath:"#/properties/onvif_username/maxLength",keyword:"maxLength",params:{limit:63},message:"must NOT have more than 63 characters"}],!1}else return l.errors=[{instancePath:s+"/onvif_username",schemaPath:"#/properties/onvif_username/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=u===r}else var n=!0;if(n){if(e.onvif_password!==void 0){let i=e.onvif_password,u=r;if(r===u)if(typeof i=="string"){if(w(i)>63)return l.errors=[{instancePath:s+"/onvif_password",schemaPath:"#/properties/onvif_password/maxLength",keyword:"maxLength",params:{limit:63},message:"must NOT have more than 63 characters"}],!1}else return l.errors=[{instancePath:s+"/onvif_password",schemaPath:"#/properties/onvif_password/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=u===r}else var n=!0;if(n){if(e.onvif_profile!==void 0){let i=e.onvif_profile,u=r;if(r===u)if(typeof i=="string"){if(w(i)>63)return l.errors=[{instancePath:s+"/onvif_profile",schemaPath:"#/properties/onvif_profile/maxLength",keyword:"maxLength",params:{limit:63},message:"must NOT have more than 63 characters"}],!1}else return l.errors=[{instancePath:s+"/onvif_profile",schemaPath:"#/properties/onvif_profile/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=u===r}else var n=!0;if(n){if(e.onvif_port!==void 0){let i=r;_(e.onvif_port,{instancePath:s+"/onvif_port",parentData:e,parentDataProperty:"onvif_port",rootData:p,dynamicAnchors:f})||(t=t===null?_.errors:t.concat(_.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.record_on_schedule!==void 0){let i=r;if(typeof e.record_on_schedule!="boolean")return l.errors=[{instancePath:s+"/record_on_schedule",schemaPath:"#/properties/record_on_schedule/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=i===r}else var n=!0;if(n){if(e.recording_schedule!==void 0){let i=r;P(e.recording_schedule,{instancePath:s+"/recording_schedule",parentData:e,parentDataProperty:"recording_schedule",rootData:p,dynamicAnchors:f})||(t=t===null?P.errors:t.concat(P.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.detection_record_on_schedule!==void 0){let i=r;if(typeof e.detection_record_on_schedule!="boolean")return l.errors=[{instancePath:s+"/detection_record_on_schedule",schemaPath:"#/properties/detection_record_on_schedule/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=i===r}else var n=!0;if(n){if(e.detection_recording_schedule!==void 0){let i=r;P(e.detection_recording_schedule,{instancePath:s+"/detection_recording_schedule",parentData:e,parentDataProperty:"detection_recording_schedule",rootData:p,dynamicAnchors:f})||(t=t===null?P.errors:t.concat(P.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.tags!==void 0){let i=e.tags,u=r;if(r===u)if(typeof i=="string"){if(w(i)>255)return l.errors=[{instancePath:s+"/tags",schemaPath:"#/properties/tags/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1}else return l.errors=[{instancePath:s+"/tags",schemaPath:"#/properties/tags/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=u===r}else var n=!0;if(n){if(e.admin_url!==void 0){let i=e.admin_url,u=r;if(r===u)if(typeof i=="string"){if(w(i)>511)return l.errors=[{instancePath:s+"/admin_url",schemaPath:"#/properties/admin_url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return l.errors=[{instancePath:s+"/admin_url",schemaPath:"#/properties/admin_url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=u===r}else var n=!0;if(n){if(e.privacy_mode!==void 0){let i=r;if(typeof e.privacy_mode!="boolean")return l.errors=[{instancePath:s+"/privacy_mode",schemaPath:"#/properties/privacy_mode/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=i===r}else var n=!0;if(n){if(e.can_control_privacy!==void 0){let i=r;if(typeof e.can_control_privacy!="boolean")return l.errors=[{instancePath:s+"/can_control_privacy",schemaPath:"#/properties/can_control_privacy/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=i===r}else var n=!0;if(n){if(e.motion_trigger_source!==void 0){let i=e.motion_trigger_source,u=r;if(r===u)if(typeof i=="string"){if(w(i)>255)return l.errors=[{instancePath:s+"/motion_trigger_source",schemaPath:"#/properties/motion_trigger_source/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1}else return l.errors=[{instancePath:s+"/motion_trigger_source",schemaPath:"#/properties/motion_trigger_source/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=u===r}else var n=!0;if(n){if(e.go2rtc_source_override!==void 0){let i=e.go2rtc_source_override,u=r;if(r===u)if(typeof i=="string"){if(w(i)>8191)return l.errors=[{instancePath:s+"/go2rtc_source_override",schemaPath:"#/properties/go2rtc_source_override/maxLength",keyword:"maxLength",params:{limit:8191},message:"must NOT have more than 8191 characters"}],!1}else return l.errors=[{instancePath:s+"/go2rtc_source_override",schemaPath:"#/properties/go2rtc_source_override/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=u===r}else var n=!0;if(n){if(e.sub_stream_url!==void 0){let i=e.sub_stream_url,u=r;if(r===u)if(typeof i=="string"){if(w(i)>511)return l.errors=[{instancePath:s+"/sub_stream_url",schemaPath:"#/properties/sub_stream_url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return l.errors=[{instancePath:s+"/sub_stream_url",schemaPath:"#/properties/sub_stream_url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=u===r}else var n=!0;if(n){if(e.has_sub_stream!==void 0){let i=r;if(typeof e.has_sub_stream!="boolean")return l.errors=[{instancePath:s+"/has_sub_stream",schemaPath:"#/properties/has_sub_stream/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=i===r}else var n=!0;if(n){if(e.detection_url!==void 0){let i=e.detection_url,u=r;if(r===u)if(typeof i=="string"){if(w(i)>511)return l.errors=[{instancePath:s+"/detection_url",schemaPath:"#/properties/detection_url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return l.errors=[{instancePath:s+"/detection_url",schemaPath:"#/properties/detection_url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=u===r}else var n=!0;if(n){if(e.publish_url!==void 0){let i=e.publish_url,u=r;if(r===u)if(typeof i=="string"){if(w(i)>511)return l.errors=[{instancePath:s+"/publish_url",schemaPath:"#/properties/publish_url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return l.errors=[{instancePath:s+"/publish_url",schemaPath:"#/properties/publish_url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=u===r}else var n=!0;if(n){if(e.status!==void 0){let i=r;O(e.status,{instancePath:s+"/status",parentData:e,parentDataProperty:"status",rootData:p,dynamicAnchors:f})||(t=t===null?O.errors:t.concat(O.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.error_message!==void 0){let i=e.error_message,u=r;if(r===u)if(typeof i=="string"){if(w(i)>255)return l.errors=[{instancePath:s+"/error_message",schemaPath:"#/properties/error_message/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1;if(w(i)<1)return l.errors=[{instancePath:s+"/error_message",schemaPath:"#/properties/error_message/minLength",keyword:"minLength",params:{limit:1},message:"must NOT have fewer than 1 characters"}],!1}else return l.errors=[{instancePath:s+"/error_message",schemaPath:"#/properties/error_message/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=u===r}else var n=!0;if(n)if(e.go2rtc_hls_available!==void 0){let i=r;if(typeof e.go2rtc_hls_available!="boolean")return l.errors=[{instancePath:s+"/go2rtc_hls_available",schemaPath:"#/properties/go2rtc_hls_available/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=i===r}else var n=!0}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}else return l.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return l.errors=t,r===0}l.evaluated={props:{camera_uuid:!0,location_uuid:!0,name:!0,url:!0,enabled:!0,streaming_enabled:!0,playback_transport:!0,eptz_config:!0,width:!0,height:!0,fps:!0,codec:!0,priority:!0,record:!0,segment_duration:!0,detection_based_recording:!0,detection_model:!0,detection_threshold:!0,detection_interval:!0,pre_detection_buffer:!0,post_detection_buffer:!0,detection_object_filter:!0,detection_object_filter_list:!0,protocol:!0,record_audio:!0,audio_voice_enhancement:!0,isOnvif:!0,backchannel_enabled:!0,retention_days:!0,detection_retention_days:!0,max_storage_mb:!0,tier_critical_multiplier:!0,tier_important_multiplier:!0,tier_ephemeral_multiplier:!0,storage_priority:!0,ptz_enabled:!0,ptz_max_x:!0,ptz_max_y:!0,ptz_max_z:!0,ptz_has_home:!0,onvif_username:!0,onvif_password:!0,onvif_profile:!0,onvif_port:!0,record_on_schedule:!0,recording_schedule:!0,detection_record_on_schedule:!0,detection_recording_schedule:!0,tags:!0,admin_url:!0,privacy_mode:!0,can_control_privacy:!0,motion_trigger_source:!0,go2rtc_source_override:!0,sub_stream_url:!0,has_sub_stream:!0,detection_url:!0,publish_url:!0,status:!0,error_message:!0,go2rtc_hls_available:!0},dynamicProps:!1,dynamicItems:!1};var H=new RegExp("^(?![A-Za-z][A-Za-z0-9+.-]*://[^/?#]*@)","u");function N(e,{instancePath:s="",parentData:b,parentDataProperty:v,rootData:p=e,dynamicAnchors:f={}}={}){let t=null,r=0,o=N.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(typeof e=="string"){if(w(e)>511)return N.errors=[{instancePath:s,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1;if(!H.test(e))return N.errors=[{instancePath:s,schemaPath:"#/pattern",keyword:"pattern",params:{pattern:"^(?![A-Za-z][A-Za-z0-9+.-]*://[^/?#]*@)"},message:'must match pattern "^(?![A-Za-z][A-Za-z0-9+.-]*://[^/?#]*@)"'}],!1}else return N.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return N.errors=t,r===0}N.evaluated={dynamicProps:!1,dynamicItems:!1};var B=new RegExp("[?&;][^=&#]*([Pp][Aa][Ss][Ss]|[Pp][Ww][Dd]|[Tt][Oo][Kk][Ee][Nn]|[Ss][Ee][Cc][Rr][Ee][Tt]|[Aa][Pp][Ii]_?[Kk][Ee][Yy])[^=&#]*=[^&#]","u");function j(e,{instancePath:s="",parentData:b,parentDataProperty:v,rootData:p=e,dynamicAnchors:f={}}={}){let t=null,r=0,o=j.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e!="string")return j.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;let d=r,y=r;if(typeof e=="string"&&!B.test(e)){let c={};t===null?t=[c]:t.push(c),r++}var a=y===r;return a?(j.errors=[{instancePath:s,schemaPath:"#/not",keyword:"not",params:{},message:"must NOT be valid"}],!1):(r=d,t!==null&&(d?t.length=d:t=null),j.errors=t,r===0)}j.evaluated={dynamicProps:!1,dynamicItems:!1};function S(e,{instancePath:s="",parentData:b,parentDataProperty:v,rootData:p=e,dynamicAnchors:f={}}={}){let t=null,r=0,o=S.evaluated;o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0);let d=r;N(e,{instancePath:s,parentData:b,parentDataProperty:v,rootData:p,dynamicAnchors:f})||(t=t===null?N.errors:t.concat(N.errors),r=t.length);var y=d===r;if(y){let a=r;j(e,{instancePath:s,parentData:b,parentDataProperty:v,rootData:p,dynamicAnchors:f})||(t=t===null?j.errors:t.concat(j.errors),r=t.length);var y=a===r}return S.errors=t,r===0}S.evaluated={dynamicProps:!1,dynamicItems:!1};function g(e,{instancePath:s="",parentData:b,parentDataProperty:v,rootData:p=e,dynamicAnchors:f={}}={}){let t=null,r=0,o=g.evaluated;return o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e!="string"?(g.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1):e!==""?(g.errors=[{instancePath:s,schemaPath:"#/const",keyword:"const",params:{allowedValue:""},message:"must be equal to constant"}],!1):(g.errors=t,r===0)}g.evaluated={dynamicProps:!1,dynamicItems:!1};function L(e,{instancePath:s="",parentData:b,parentDataProperty:v,rootData:p=e,dynamicAnchors:f={}}={}){let t=null,r=0,o=L.evaluated;o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0);let d=r;l(e,{instancePath:s,parentData:b,parentDataProperty:v,rootData:p,dynamicAnchors:f})||(t=t===null?l.errors:t.concat(l.errors),r=t.length);var y=d===r;if(y){let F=r,E=r,n=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let i;if(e.detection_api_url===void 0&&(i="detection_api_url")){let u={};t===null?t=[u]:t.push(u),r++}}var a=n===r;if(a)return L.errors=[{instancePath:s,schemaPath:"#/allOf/1/not",keyword:"not",params:{},message:"must NOT be valid"}],!1;if(r=E,t!==null&&(E?t.length=E:t=null),r===F)if(e&&typeof e=="object"&&!Array.isArray(e)){if(e.can_control_privacy!==void 0){let i=r;if(e.can_control_privacy!==!1)return L.errors=[{instancePath:s+"/can_control_privacy",schemaPath:"#/allOf/1/properties/can_control_privacy/const",keyword:"const",params:{allowedValue:!1},message:"must be equal to constant"}],!1;var c=i===r}else var c=!0;if(c){if(e.url!==void 0){let i=r;S(e.url,{instancePath:s+"/url",parentData:e,parentDataProperty:"url",rootData:p,dynamicAnchors:f})||(t=t===null?S.errors:t.concat(S.errors),r=t.length);var c=i===r}else var c=!0;if(c){if(e.onvif_username!==void 0){let i=r;g(e.onvif_username,{instancePath:s+"/onvif_username",parentData:e,parentDataProperty:"onvif_username",rootData:p,dynamicAnchors:f})||(t=t===null?g.errors:t.concat(g.errors),r=t.length);var c=i===r}else var c=!0;if(c){if(e.onvif_password!==void 0){let i=r;g(e.onvif_password,{instancePath:s+"/onvif_password",parentData:e,parentDataProperty:"onvif_password",rootData:p,dynamicAnchors:f})||(t=t===null?g.errors:t.concat(g.errors),r=t.length);var c=i===r}else var c=!0;if(c){if(e.admin_url!==void 0){let i=r;g(e.admin_url,{instancePath:s+"/admin_url",parentData:e,parentDataProperty:"admin_url",rootData:p,dynamicAnchors:f})||(t=t===null?g.errors:t.concat(g.errors),r=t.length);var c=i===r}else var c=!0;if(c){if(e.go2rtc_source_override!==void 0){let i=r;g(e.go2rtc_source_override,{instancePath:s+"/go2rtc_source_override",parentData:e,parentDataProperty:"go2rtc_source_override",rootData:p,dynamicAnchors:f})||(t=t===null?g.errors:t.concat(g.errors),r=t.length);var c=i===r}else var c=!0;if(c){if(e.sub_stream_url!==void 0){let i=r;g(e.sub_stream_url,{instancePath:s+"/sub_stream_url",parentData:e,parentDataProperty:"sub_stream_url",rootData:p,dynamicAnchors:f})||(t=t===null?g.errors:t.concat(g.errors),r=t.length);var c=i===r}else var c=!0;if(c){if(e.detection_url!==void 0){let i=r;g(e.detection_url,{instancePath:s+"/detection_url",parentData:e,parentDataProperty:"detection_url",rootData:p,dynamicAnchors:f})||(t=t===null?g.errors:t.concat(g.errors),r=t.length);var c=i===r}else var c=!0;if(c)if(e.publish_url!==void 0){let i=r;g(e.publish_url,{instancePath:s+"/publish_url",parentData:e,parentDataProperty:"publish_url",rootData:p,dynamicAnchors:f})||(t=t===null?g.errors:t.concat(g.errors),r=t.length);var c=i===r}else var c=!0}}}}}}}}else return L.errors=[{instancePath:s,schemaPath:"#/allOf/1/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;var y=F===r}return L.errors=t,r===0}L.evaluated={props:{can_control_privacy:!0,url:!0,onvif_username:!0,onvif_password:!0,admin_url:!0,go2rtc_source_override:!0,sub_stream_url:!0,detection_url:!0,publish_url:!0,camera_uuid:!0,location_uuid:!0,name:!0,enabled:!0,streaming_enabled:!0,playback_transport:!0,eptz_config:!0,width:!0,height:!0,fps:!0,codec:!0,priority:!0,record:!0,segment_duration:!0,detection_based_recording:!0,detection_model:!0,detection_threshold:!0,detection_interval:!0,pre_detection_buffer:!0,post_detection_buffer:!0,detection_object_filter:!0,detection_object_filter_list:!0,protocol:!0,record_audio:!0,audio_voice_enhancement:!0,isOnvif:!0,backchannel_enabled:!0,retention_days:!0,detection_retention_days:!0,max_storage_mb:!0,tier_critical_multiplier:!0,tier_important_multiplier:!0,tier_ephemeral_multiplier:!0,storage_priority:!0,ptz_enabled:!0,ptz_max_x:!0,ptz_max_y:!0,ptz_max_z:!0,ptz_has_home:!0,onvif_profile:!0,onvif_port:!0,record_on_schedule:!0,recording_schedule:!0,detection_record_on_schedule:!0,detection_recording_schedule:!0,tags:!0,privacy_mode:!0,motion_trigger_source:!0,has_sub_stream:!0,status:!0,error_message:!0,go2rtc_hls_available:!0},dynamicProps:!1,dynamicItems:!1};function T(e,{instancePath:s="",parentData:b,parentDataProperty:v,rootData:p=e,dynamicAnchors:f={}}={}){let t=null,r=0,o=T.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(e&&typeof e=="object"&&!Array.isArray(e)){let d;if(e.stream===void 0&&(d="stream"))return T.errors=[{instancePath:s,schemaPath:"#/required",keyword:"required",params:{missingProperty:d},message:"must have required property '"+d+"'"}],!1;e.stream!==void 0&&(L(e.stream,{instancePath:s+"/stream",parentData:e,parentDataProperty:"stream",rootData:p,dynamicAnchors:f})||(t=t===null?L.errors:t.concat(L.errors),r=t.length))}else return T.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return T.errors=t,r===0}T.evaluated={props:{stream:!0},dynamicProps:!1,dynamicItems:!1};export{Q as validateStreamFullResponseLiveOnly}; diff --git a/web/js/api/generated/validators/StreamName.d.mts b/web/js/api/generated/validators/StreamName.d.mts new file mode 100644 index 000000000..d0b377bf6 --- /dev/null +++ b/web/js/api/generated/validators/StreamName.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateStreamName } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/StreamName.mjs b/web/js/api/generated/validators/StreamName.mjs new file mode 100644 index 000000000..cd12748f2 --- /dev/null +++ b/web/js/api/generated/validators/StreamName.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var f=(t,e)=>()=>{try{return e||t((e={exports:{}}).exports,e),e.exports}catch(n){throw e=0,n}};var c=f(m=>{"use strict";Object.defineProperty(m,"__esModule",{value:!0});function o(t){let e=t.length,n=0,a=0,s;for(;a=55296&&s<=56319&&a255)return r.errors=[{instancePath:e,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1;if(u(t)<1)return r.errors=[{instancePath:e,schemaPath:"#/minLength",keyword:"minLength",params:{limit:1},message:"must NOT have fewer than 1 characters"}],!1;if(new TextEncoder().encode(t).byteLength>255)return r.errors=[{instancePath:e,schemaPath:"#/x-lightnvr-max-bytes",keyword:"x-lightnvr-max-bytes",params:{},message:"must fit the UTF-8 byte limit"}],!1}else return r.errors=[{instancePath:e,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return r.errors=h,l===0}r.evaluated={dynamicProps:!1,dynamicItems:!1};export{p as validateStreamName}; diff --git a/web/js/api/generated/validators/StreamPrivacyModeRequest.d.mts b/web/js/api/generated/validators/StreamPrivacyModeRequest.d.mts new file mode 100644 index 000000000..83e95f574 --- /dev/null +++ b/web/js/api/generated/validators/StreamPrivacyModeRequest.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateStreamPrivacyModeRequest } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/StreamPrivacyModeRequest.mjs b/web/js/api/generated/validators/StreamPrivacyModeRequest.mjs new file mode 100644 index 000000000..8ccc38456 --- /dev/null +++ b/web/js/api/generated/validators/StreamPrivacyModeRequest.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var l=r;function r(e,{instancePath:t="",parentData:n,parentDataProperty:m,rootData:y=e,dynamicAnchors:c={}}={}){let p=null,s=0,o=r.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),s===0)if(e&&typeof e=="object"&&!Array.isArray(e)){let a;if(e.set_privacy_mode===void 0&&(a="set_privacy_mode"))return r.errors=[{instancePath:t,schemaPath:"#/required",keyword:"required",params:{missingProperty:a},message:"must have required property '"+a+"'"}],!1;{let d=s;for(let i in e)if(i!=="set_privacy_mode"){return r.errors=[{instancePath:t,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty:i},message:"must NOT have additional properties"}],!1;break}if(d===s&&e.set_privacy_mode!==void 0&&typeof e.set_privacy_mode!="boolean")return r.errors=[{instancePath:t+"/set_privacy_mode",schemaPath:"#/properties/set_privacy_mode/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1}}else return r.errors=[{instancePath:t,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return r.errors=p,s===0}r.evaluated={props:!0,dynamicProps:!1,dynamicItems:!1};export{l as validateStreamPrivacyModeRequest}; diff --git a/web/js/api/generated/validators/StreamPrivacyModeResponse.d.mts b/web/js/api/generated/validators/StreamPrivacyModeResponse.d.mts new file mode 100644 index 000000000..ca6ec7287 --- /dev/null +++ b/web/js/api/generated/validators/StreamPrivacyModeResponse.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateStreamPrivacyModeResponse } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/StreamPrivacyModeResponse.mjs b/web/js/api/generated/validators/StreamPrivacyModeResponse.mjs new file mode 100644 index 000000000..b1f3ff794 --- /dev/null +++ b/web/js/api/generated/validators/StreamPrivacyModeResponse.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var y=r;function r(e,{instancePath:t="",parentData:p,parentDataProperty:u,rootData:m=e,dynamicAnchors:d={}}={}){let n=null,s=0,o=r.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),s===0)if(e&&typeof e=="object"&&!Array.isArray(e)){let c;if(e.success===void 0&&(c="success")||e.privacy_mode===void 0&&(c="privacy_mode"))return r.errors=[{instancePath:t,schemaPath:"#/required",keyword:"required",params:{missingProperty:c},message:"must have required property '"+c+"'"}],!1;if(e.success!==void 0){let i=s;if(e.success!==!0)return r.errors=[{instancePath:t+"/success",schemaPath:"#/properties/success/const",keyword:"const",params:{allowedValue:!0},message:"must be equal to constant"}],!1;var a=i===s}else var a=!0;if(a)if(e.privacy_mode!==void 0){let i=s;if(typeof e.privacy_mode!="boolean")return r.errors=[{instancePath:t+"/privacy_mode",schemaPath:"#/properties/privacy_mode/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var a=i===s}else var a=!0}else return r.errors=[{instancePath:t,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return r.errors=n,s===0}r.evaluated={props:{success:!0,privacy_mode:!0},dynamicProps:!1,dynamicItems:!1};export{y as validateStreamPrivacyModeResponse}; diff --git a/web/js/api/generated/validators/StreamProtocol.d.mts b/web/js/api/generated/validators/StreamProtocol.d.mts new file mode 100644 index 000000000..f5385785a --- /dev/null +++ b/web/js/api/generated/validators/StreamProtocol.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateStreamProtocol } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/StreamProtocol.mjs b/web/js/api/generated/validators/StreamProtocol.mjs new file mode 100644 index 000000000..9508bacfe --- /dev/null +++ b/web/js/api/generated/validators/StreamProtocol.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var c=r,o={type:"integer",enum:[0,1],$id:"urn:lightnvr:contract:StreamProtocol"};function r(e,{instancePath:s="",parentData:i,parentDataProperty:m,rootData:l=e,dynamicAnchors:u={}}={}){let a=null,n=0,t=r.evaluated;return t.dynamicProps&&(t.props=void 0),t.dynamicItems&&(t.items=void 0),typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)?e===0||e===1?(r.errors=a,n===0):(r.errors=[{instancePath:s,schemaPath:"#/enum",keyword:"enum",params:{allowedValues:o.enum},message:"must be equal to one of the allowed values"}],!1):(r.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1)}r.evaluated={dynamicProps:!1,dynamicItems:!1};export{c as validateStreamProtocol}; diff --git a/web/js/api/generated/validators/StreamSummaryAdmin.d.mts b/web/js/api/generated/validators/StreamSummaryAdmin.d.mts new file mode 100644 index 000000000..55b93e00d --- /dev/null +++ b/web/js/api/generated/validators/StreamSummaryAdmin.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateStreamSummaryAdmin } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/StreamSummaryAdmin.mjs b/web/js/api/generated/validators/StreamSummaryAdmin.mjs new file mode 100644 index 000000000..f27e40008 --- /dev/null +++ b/web/js/api/generated/validators/StreamSummaryAdmin.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var V=(e,n)=>()=>{try{return n||e((n={exports:{}}).exports,n),n.exports}catch(y){throw n=0,y}};var q=V(E=>{"use strict";Object.defineProperty(E,"__esModule",{value:!0});function j(e){let n=e.length,y=0,d=0,u;for(;d=55296&&u<=56319&&d36)return v.errors=[{instancePath:n,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:36},message:"must NOT have more than 36 characters"}],!1;if(!Z.test(e))return v.errors=[{instancePath:n,schemaPath:"#/pattern",keyword:"pattern",params:{pattern:"^(|[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})$"},message:'must match pattern "^(|[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})$"'}],!1}else return v.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return v.errors=t,r===0}v.evaluated={dynamicProps:!1,dynamicItems:!1};function P(e,{instancePath:n="",parentData:y,parentDataProperty:d,rootData:u=e,dynamicAnchors:c={}}={}){let t=null,r=0,o=P.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(typeof e=="string"){if(A(e)>255)return P.errors=[{instancePath:n,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1;if(A(e)<1)return P.errors=[{instancePath:n,schemaPath:"#/minLength",keyword:"minLength",params:{limit:1},message:"must NOT have fewer than 1 characters"}],!1;if(new TextEncoder().encode(e).byteLength>255)return P.errors=[{instancePath:n,schemaPath:"#/x-lightnvr-max-bytes",keyword:"x-lightnvr-max-bytes",params:{},message:"must fit the UTF-8 byte limit"}],!1}else return P.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return P.errors=t,r===0}P.evaluated={dynamicProps:!1,dynamicItems:!1};function h(e,{instancePath:n="",parentData:y,parentDataProperty:d,rootData:u=e,dynamicAnchors:c={}}={}){let t=null,r=0,o=h.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),!(typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)))return h.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;if(r===0&&typeof e=="number"&&isFinite(e)){if(e>2147483647||isNaN(e))return h.errors=[{instancePath:n,schemaPath:"#/maximum",keyword:"maximum",params:{comparison:"<=",limit:2147483647},message:"must be <= 2147483647"}],!1;if(e<-2147483648||isNaN(e))return h.errors=[{instancePath:n,schemaPath:"#/minimum",keyword:"minimum",params:{comparison:">=",limit:-2147483648},message:"must be >= -2147483648"}],!1}return h.errors=t,r===0}h.evaluated={dynamicProps:!1,dynamicItems:!1};var K={type:"integer",enum:[0,1],$id:"urn:lightnvr:contract:StreamProtocol"};function D(e,{instancePath:n="",parentData:y,parentDataProperty:d,rootData:u=e,dynamicAnchors:c={}}={}){let t=null,r=0,o=D.evaluated;return o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)?e===0||e===1?(D.errors=t,r===0):(D.errors=[{instancePath:n,schemaPath:"#/enum",keyword:"enum",params:{allowedValues:K.enum},message:"must be equal to one of the allowed values"}],!1):(D.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1)}D.evaluated={dynamicProps:!1,dynamicItems:!1};var R={type:"string",enum:["Running","Degraded","Error","Stopped","Unknown"],$id:"urn:lightnvr:contract:SummaryStatus"};function x(e,{instancePath:n="",parentData:y,parentDataProperty:d,rootData:u=e,dynamicAnchors:c={}}={}){let t=null,r=0,o=x.evaluated;return o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e!="string"?(x.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1):e==="Running"||e==="Degraded"||e==="Error"||e==="Stopped"||e==="Unknown"?(x.errors=t,r===0):(x.errors=[{instancePath:n,schemaPath:"#/enum",keyword:"enum",params:{allowedValues:R.enum},message:"must be equal to one of the allowed values"}],!1)}x.evaluated={dynamicProps:!1,dynamicItems:!1};var B={type:"string",enum:["live","offline","never_connected","disabled"],$id:"urn:lightnvr:contract:Availability"};function O(e,{instancePath:n="",parentData:y,parentDataProperty:d,rootData:u=e,dynamicAnchors:c={}}={}){let t=null,r=0,o=O.evaluated;return o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e!="string"?(O.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1):e==="live"||e==="offline"||e==="never_connected"||e==="disabled"?(O.errors=t,r===0):(O.errors=[{instancePath:n,schemaPath:"#/enum",keyword:"enum",params:{allowedValues:B.enum},message:"must be equal to one of the allowed values"}],!1)}O.evaluated={dynamicProps:!1,dynamicItems:!1};function g(e,{instancePath:n="",parentData:y,parentDataProperty:d,rootData:u=e,dynamicAnchors:c={}}={}){let t=null,r=0,o=g.evaluated;return o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)?r===0&&typeof e=="number"&&isFinite(e)&&(e<0||isNaN(e))?(g.errors=[{instancePath:n,schemaPath:"#/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1):(g.errors=t,r===0):(g.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1)}g.evaluated={dynamicProps:!1,dynamicItems:!1};function f(e,{instancePath:n="",parentData:y,parentDataProperty:d,rootData:u=e,dynamicAnchors:c={}}={}){let t=null,r=0,o=f.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),F(e,{instancePath:n,parentData:y,parentDataProperty:d,rootData:u,dynamicAnchors:c})||(t=t===null?F.errors:t.concat(F.errors),r=t.length),r===0)if(e&&typeof e=="object"&&!Array.isArray(e)){let l;if(e.camera_uuid===void 0&&(l="camera_uuid")||e.name===void 0&&(l="name")||e.location_uuid===void 0&&(l="location_uuid")||e.location_name===void 0&&(l="location_name")||e.location_path===void 0&&(l="location_path")||e.enabled===void 0&&(l="enabled")||e.width===void 0&&(l="width")||e.height===void 0&&(l="height")||e.fps===void 0&&(l="fps")||e.codec===void 0&&(l="codec")||e.protocol===void 0&&(l="protocol")||e.record===void 0&&(l="record")||e.record_on_schedule===void 0&&(l="record_on_schedule")||e.detection_based_recording===void 0&&(l="detection_based_recording")||e.isOnvif===void 0&&(l="isOnvif")||e.tags===void 0&&(l="tags")||e.status===void 0&&(l="status")||e.availability===void 0&&(l="availability")||e.first_video_at===void 0&&(l="first_video_at")||e.last_frame_ts===void 0&&(l="last_frame_ts")||e.current_fps===void 0&&(l="current_fps")||e.recording_active===void 0&&(l="recording_active")||e.last_recording_at===void 0&&(l="last_recording_at")||e.can_configure===void 0&&(l="can_configure"))return f.errors=[{instancePath:n,schemaPath:"#/required",keyword:"required",params:{missingProperty:l},message:"must have required property '"+l+"'"}],!1;if(e.camera_uuid!==void 0){let a=r;v(e.camera_uuid,{instancePath:n+"/camera_uuid",parentData:e,parentDataProperty:"camera_uuid",rootData:u,dynamicAnchors:c})||(t=t===null?v.errors:t.concat(v.errors),r=t.length);var s=a===r}else var s=!0;if(s){if(e.name!==void 0){let a=r;P(e.name,{instancePath:n+"/name",parentData:e,parentDataProperty:"name",rootData:u,dynamicAnchors:c})||(t=t===null?P.errors:t.concat(P.errors),r=t.length);var s=a===r}else var s=!0;if(s){if(e.location_uuid!==void 0){let a=r;v(e.location_uuid,{instancePath:n+"/location_uuid",parentData:e,parentDataProperty:"location_uuid",rootData:u,dynamicAnchors:c})||(t=t===null?v.errors:t.concat(v.errors),r=t.length);var s=a===r}else var s=!0;if(s){if(e.location_name!==void 0){let a=e.location_name,i=r;if(r===i)if(typeof a=="string"){if(A(a)>127)return f.errors=[{instancePath:n+"/location_name",schemaPath:"#/properties/location_name/maxLength",keyword:"maxLength",params:{limit:127},message:"must NOT have more than 127 characters"}],!1}else return f.errors=[{instancePath:n+"/location_name",schemaPath:"#/properties/location_name/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var s=i===r}else var s=!0;if(s){if(e.location_path!==void 0){let a=e.location_path,i=r;if(r===i)if(typeof a=="string"){if(A(a)>1023)return f.errors=[{instancePath:n+"/location_path",schemaPath:"#/properties/location_path/maxLength",keyword:"maxLength",params:{limit:1023},message:"must NOT have more than 1023 characters"}],!1}else return f.errors=[{instancePath:n+"/location_path",schemaPath:"#/properties/location_path/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var s=i===r}else var s=!0;if(s){if(e.enabled!==void 0){let a=r;if(typeof e.enabled!="boolean")return f.errors=[{instancePath:n+"/enabled",schemaPath:"#/properties/enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var s=a===r}else var s=!0;if(s){if(e.width!==void 0){let a=r;h(e.width,{instancePath:n+"/width",parentData:e,parentDataProperty:"width",rootData:u,dynamicAnchors:c})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var s=a===r}else var s=!0;if(s){if(e.height!==void 0){let a=r;h(e.height,{instancePath:n+"/height",parentData:e,parentDataProperty:"height",rootData:u,dynamicAnchors:c})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var s=a===r}else var s=!0;if(s){if(e.fps!==void 0){let a=r;h(e.fps,{instancePath:n+"/fps",parentData:e,parentDataProperty:"fps",rootData:u,dynamicAnchors:c})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var s=a===r}else var s=!0;if(s){if(e.codec!==void 0){let a=e.codec,i=r;if(r===i)if(typeof a=="string"){if(A(a)>15)return f.errors=[{instancePath:n+"/codec",schemaPath:"#/properties/codec/maxLength",keyword:"maxLength",params:{limit:15},message:"must NOT have more than 15 characters"}],!1}else return f.errors=[{instancePath:n+"/codec",schemaPath:"#/properties/codec/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var s=i===r}else var s=!0;if(s){if(e.protocol!==void 0){let a=r;D(e.protocol,{instancePath:n+"/protocol",parentData:e,parentDataProperty:"protocol",rootData:u,dynamicAnchors:c})||(t=t===null?D.errors:t.concat(D.errors),r=t.length);var s=a===r}else var s=!0;if(s){if(e.record!==void 0){let a=r;if(typeof e.record!="boolean")return f.errors=[{instancePath:n+"/record",schemaPath:"#/properties/record/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var s=a===r}else var s=!0;if(s){if(e.record_on_schedule!==void 0){let a=r;if(typeof e.record_on_schedule!="boolean")return f.errors=[{instancePath:n+"/record_on_schedule",schemaPath:"#/properties/record_on_schedule/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var s=a===r}else var s=!0;if(s){if(e.detection_based_recording!==void 0){let a=r;if(typeof e.detection_based_recording!="boolean")return f.errors=[{instancePath:n+"/detection_based_recording",schemaPath:"#/properties/detection_based_recording/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var s=a===r}else var s=!0;if(s){if(e.isOnvif!==void 0){let a=r;if(typeof e.isOnvif!="boolean")return f.errors=[{instancePath:n+"/isOnvif",schemaPath:"#/properties/isOnvif/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var s=a===r}else var s=!0;if(s){if(e.tags!==void 0){let a=e.tags,i=r;if(r===i)if(typeof a=="string"){if(A(a)>255)return f.errors=[{instancePath:n+"/tags",schemaPath:"#/properties/tags/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1}else return f.errors=[{instancePath:n+"/tags",schemaPath:"#/properties/tags/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var s=i===r}else var s=!0;if(s){if(e.status!==void 0){let a=r;x(e.status,{instancePath:n+"/status",parentData:e,parentDataProperty:"status",rootData:u,dynamicAnchors:c})||(t=t===null?x.errors:t.concat(x.errors),r=t.length);var s=a===r}else var s=!0;if(s){if(e.availability!==void 0){let a=r;O(e.availability,{instancePath:n+"/availability",parentData:e,parentDataProperty:"availability",rootData:u,dynamicAnchors:c})||(t=t===null?O.errors:t.concat(O.errors),r=t.length);var s=a===r}else var s=!0;if(s){if(e.first_video_at!==void 0){let a=r;g(e.first_video_at,{instancePath:n+"/first_video_at",parentData:e,parentDataProperty:"first_video_at",rootData:u,dynamicAnchors:c})||(t=t===null?g.errors:t.concat(g.errors),r=t.length);var s=a===r}else var s=!0;if(s){if(e.last_frame_ts!==void 0){let a=r;g(e.last_frame_ts,{instancePath:n+"/last_frame_ts",parentData:e,parentDataProperty:"last_frame_ts",rootData:u,dynamicAnchors:c})||(t=t===null?g.errors:t.concat(g.errors),r=t.length);var s=a===r}else var s=!0;if(s){if(e.current_fps!==void 0){let a=e.current_fps,i=r;if(r===i)if(typeof a=="number"&&isFinite(a)){if(a<0||isNaN(a))return f.errors=[{instancePath:n+"/current_fps",schemaPath:"#/properties/current_fps/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1}else return f.errors=[{instancePath:n+"/current_fps",schemaPath:"#/properties/current_fps/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var s=i===r}else var s=!0;if(s){if(e.recording_active!==void 0){let a=r;if(typeof e.recording_active!="boolean")return f.errors=[{instancePath:n+"/recording_active",schemaPath:"#/properties/recording_active/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var s=a===r}else var s=!0;if(s){if(e.last_recording_at!==void 0){let a=r;g(e.last_recording_at,{instancePath:n+"/last_recording_at",parentData:e,parentDataProperty:"last_recording_at",rootData:u,dynamicAnchors:c})||(t=t===null?g.errors:t.concat(g.errors),r=t.length);var s=a===r}else var s=!0;if(s)if(e.can_configure!==void 0){let a=r;if(typeof e.can_configure!="boolean")return f.errors=[{instancePath:n+"/can_configure",schemaPath:"#/properties/can_configure/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var s=a===r}else var s=!0}}}}}}}}}}}}}}}}}}}}}}}else return f.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return f.errors=t,r===0}f.evaluated={props:{camera_uuid:!0,name:!0,location_uuid:!0,location_name:!0,location_path:!0,enabled:!0,width:!0,height:!0,fps:!0,codec:!0,protocol:!0,record:!0,record_on_schedule:!0,detection_based_recording:!0,isOnvif:!0,tags:!0,status:!0,availability:!0,first_video_at:!0,last_frame_ts:!0,current_fps:!0,recording_active:!0,last_recording_at:!0,can_configure:!0},dynamicProps:!1,dynamicItems:!1};function $(e,{instancePath:n="",parentData:y,parentDataProperty:d,rootData:u=e,dynamicAnchors:c={}}={}){let t=null,r=0,o=$.evaluated;return o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e!="string"?($.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1):e!==""?($.errors=[{instancePath:n,schemaPath:"#/const",keyword:"const",params:{allowedValue:""},message:"must be equal to constant"}],!1):($.errors=t,r===0)}$.evaluated={dynamicProps:!1,dynamicItems:!1};function k(e,{instancePath:n="",parentData:y,parentDataProperty:d,rootData:u=e,dynamicAnchors:c={}}={}){let t=null,r=0,o=k.evaluated;o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0);let s=r;f(e,{instancePath:n,parentData:y,parentDataProperty:d,rootData:u,dynamicAnchors:c})||(t=t===null?f.errors:t.concat(f.errors),r=t.length);var l=s===r;if(l){let L=r,S=r,I=!0,N=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let b;if(e.can_configure===void 0&&(b="can_configure")){let w={};t===null?t=[w]:t.push(w),r++}else if(e.can_configure!==void 0&&e.can_configure!==!1){let w={};t===null?t=[w]:t.push(w),r++}}var a=N===r;if(r=S,t!==null&&(S?t.length=S:t=null),a){let b=r;e&&typeof e=="object"&&!Array.isArray(e)&&e.admin_url!==void 0&&($(e.admin_url,{instancePath:n+"/admin_url",parentData:e,parentDataProperty:"admin_url",rootData:u,dynamicAnchors:c})||(t=t===null?$.errors:t.concat($.errors),r=t.length));var a=b===r;if(I=a,I){var i={};i.admin_url=!0,i.can_configure=!0}}if(!I){let b={instancePath:n,schemaPath:"#/allOf/1/if",keyword:"if",params:{failingKeyword:"then"},message:'must match "then" schema'};return t===null?t=[b]:t.push(b),r++,k.errors=t,!1}if(r===L)if(e&&typeof e=="object"&&!Array.isArray(e)){if(i!==!0&&(i=i||{},i.admin_url=!0),e.admin_url!==void 0){let b=e.admin_url;if(r===r)if(typeof b=="string"){if(A(b)>511)return k.errors=[{instancePath:n+"/admin_url",schemaPath:"#/allOf/1/properties/admin_url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return k.errors=[{instancePath:n+"/admin_url",schemaPath:"#/allOf/1/properties/admin_url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1}}else return k.errors=[{instancePath:n,schemaPath:"#/allOf/1/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;var l=L===r;l&&i!==!0&&(i=i||{},i.camera_uuid=!0,i.name=!0,i.location_uuid=!0,i.location_name=!0,i.location_path=!0,i.enabled=!0,i.width=!0,i.height=!0,i.fps=!0,i.codec=!0,i.protocol=!0,i.record=!0,i.record_on_schedule=!0,i.detection_based_recording=!0,i.isOnvif=!0,i.tags=!0,i.status=!0,i.availability=!0,i.first_video_at=!0,i.last_frame_ts=!0,i.current_fps=!0,i.recording_active=!0,i.last_recording_at=!0,i.can_configure=!0)}return k.errors=t,o.props=i,r===0}k.evaluated={dynamicProps:!0,dynamicItems:!1};export{G as validateStreamSummaryAdmin}; diff --git a/web/js/api/generated/validators/StreamSummaryBase.d.mts b/web/js/api/generated/validators/StreamSummaryBase.d.mts new file mode 100644 index 000000000..e2b58ba0e --- /dev/null +++ b/web/js/api/generated/validators/StreamSummaryBase.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateStreamSummaryBase } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/StreamSummaryBase.mjs b/web/js/api/generated/validators/StreamSummaryBase.mjs new file mode 100644 index 000000000..bc15152b3 --- /dev/null +++ b/web/js/api/generated/validators/StreamSummaryBase.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var U=(e,s)=>()=>{try{return s||e((s={exports:{}}).exports,s),s.exports}catch(v){throw s=0,v}};var $=U(x=>{"use strict";Object.defineProperty(x,"__esModule",{value:!0});function F(e){let s=e.length,v=0,d=0,u;for(;d=55296&&u<=56319&&d36)return g.errors=[{instancePath:s,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:36},message:"must NOT have more than 36 characters"}],!1;if(!C.test(e))return g.errors=[{instancePath:s,schemaPath:"#/pattern",keyword:"pattern",params:{pattern:"^(|[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})$"},message:'must match pattern "^(|[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})$"'}],!1}else return g.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return g.errors=t,r===0}g.evaluated={dynamicProps:!1,dynamicItems:!1};function b(e,{instancePath:s="",parentData:v,parentDataProperty:d,rootData:u=e,dynamicAnchors:m={}}={}){let t=null,r=0,a=b.evaluated;if(a.dynamicProps&&(a.props=void 0),a.dynamicItems&&(a.items=void 0),r===0)if(typeof e=="string"){if(A(e)>255)return b.errors=[{instancePath:s,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1;if(A(e)<1)return b.errors=[{instancePath:s,schemaPath:"#/minLength",keyword:"minLength",params:{limit:1},message:"must NOT have fewer than 1 characters"}],!1;if(new TextEncoder().encode(e).byteLength>255)return b.errors=[{instancePath:s,schemaPath:"#/x-lightnvr-max-bytes",keyword:"x-lightnvr-max-bytes",params:{},message:"must fit the UTF-8 byte limit"}],!1}else return b.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return b.errors=t,r===0}b.evaluated={dynamicProps:!1,dynamicItems:!1};function _(e,{instancePath:s="",parentData:v,parentDataProperty:d,rootData:u=e,dynamicAnchors:m={}}={}){let t=null,r=0,a=_.evaluated;if(a.dynamicProps&&(a.props=void 0),a.dynamicItems&&(a.items=void 0),!(typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)))return _.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;if(r===0&&typeof e=="number"&&isFinite(e)){if(e>2147483647||isNaN(e))return _.errors=[{instancePath:s,schemaPath:"#/maximum",keyword:"maximum",params:{comparison:"<=",limit:2147483647},message:"must be <= 2147483647"}],!1;if(e<-2147483648||isNaN(e))return _.errors=[{instancePath:s,schemaPath:"#/minimum",keyword:"minimum",params:{comparison:">=",limit:-2147483648},message:"must be >= -2147483648"}],!1}return _.errors=t,r===0}_.evaluated={dynamicProps:!1,dynamicItems:!1};var Z={type:"integer",enum:[0,1],$id:"urn:lightnvr:contract:StreamProtocol"};function P(e,{instancePath:s="",parentData:v,parentDataProperty:d,rootData:u=e,dynamicAnchors:m={}}={}){let t=null,r=0,a=P.evaluated;return a.dynamicProps&&(a.props=void 0),a.dynamicItems&&(a.items=void 0),typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)?e===0||e===1?(P.errors=t,r===0):(P.errors=[{instancePath:s,schemaPath:"#/enum",keyword:"enum",params:{allowedValues:Z.enum},message:"must be equal to one of the allowed values"}],!1):(P.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1)}P.evaluated={dynamicProps:!1,dynamicItems:!1};var R={type:"string",enum:["Running","Degraded","Error","Stopped","Unknown"],$id:"urn:lightnvr:contract:SummaryStatus"};function w(e,{instancePath:s="",parentData:v,parentDataProperty:d,rootData:u=e,dynamicAnchors:m={}}={}){let t=null,r=0,a=w.evaluated;return a.dynamicProps&&(a.props=void 0),a.dynamicItems&&(a.items=void 0),typeof e!="string"?(w.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1):e==="Running"||e==="Degraded"||e==="Error"||e==="Stopped"||e==="Unknown"?(w.errors=t,r===0):(w.errors=[{instancePath:s,schemaPath:"#/enum",keyword:"enum",params:{allowedValues:R.enum},message:"must be equal to one of the allowed values"}],!1)}w.evaluated={dynamicProps:!1,dynamicItems:!1};var V={type:"string",enum:["live","offline","never_connected","disabled"],$id:"urn:lightnvr:contract:Availability"};function k(e,{instancePath:s="",parentData:v,parentDataProperty:d,rootData:u=e,dynamicAnchors:m={}}={}){let t=null,r=0,a=k.evaluated;return a.dynamicProps&&(a.props=void 0),a.dynamicItems&&(a.items=void 0),typeof e!="string"?(k.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1):e==="live"||e==="offline"||e==="never_connected"||e==="disabled"?(k.errors=t,r===0):(k.errors=[{instancePath:s,schemaPath:"#/enum",keyword:"enum",params:{allowedValues:V.enum},message:"must be equal to one of the allowed values"}],!1)}k.evaluated={dynamicProps:!1,dynamicItems:!1};function h(e,{instancePath:s="",parentData:v,parentDataProperty:d,rootData:u=e,dynamicAnchors:m={}}={}){let t=null,r=0,a=h.evaluated;return a.dynamicProps&&(a.props=void 0),a.dynamicItems&&(a.items=void 0),typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)?r===0&&typeof e=="number"&&isFinite(e)&&(e<0||isNaN(e))?(h.errors=[{instancePath:s,schemaPath:"#/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1):(h.errors=t,r===0):(h.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1)}h.evaluated={dynamicProps:!1,dynamicItems:!1};function f(e,{instancePath:s="",parentData:v,parentDataProperty:d,rootData:u=e,dynamicAnchors:m={}}={}){let t=null,r=0,a=f.evaluated;if(a.dynamicProps&&(a.props=void 0),a.dynamicItems&&(a.items=void 0),D(e,{instancePath:s,parentData:v,parentDataProperty:d,rootData:u,dynamicAnchors:m})||(t=t===null?D.errors:t.concat(D.errors),r=t.length),r===0)if(e&&typeof e=="object"&&!Array.isArray(e)){let l;if(e.camera_uuid===void 0&&(l="camera_uuid")||e.name===void 0&&(l="name")||e.location_uuid===void 0&&(l="location_uuid")||e.location_name===void 0&&(l="location_name")||e.location_path===void 0&&(l="location_path")||e.enabled===void 0&&(l="enabled")||e.width===void 0&&(l="width")||e.height===void 0&&(l="height")||e.fps===void 0&&(l="fps")||e.codec===void 0&&(l="codec")||e.protocol===void 0&&(l="protocol")||e.record===void 0&&(l="record")||e.record_on_schedule===void 0&&(l="record_on_schedule")||e.detection_based_recording===void 0&&(l="detection_based_recording")||e.isOnvif===void 0&&(l="isOnvif")||e.tags===void 0&&(l="tags")||e.status===void 0&&(l="status")||e.availability===void 0&&(l="availability")||e.first_video_at===void 0&&(l="first_video_at")||e.last_frame_ts===void 0&&(l="last_frame_ts")||e.current_fps===void 0&&(l="current_fps")||e.recording_active===void 0&&(l="recording_active")||e.last_recording_at===void 0&&(l="last_recording_at")||e.can_configure===void 0&&(l="can_configure"))return f.errors=[{instancePath:s,schemaPath:"#/required",keyword:"required",params:{missingProperty:l},message:"must have required property '"+l+"'"}],!1;if(e.camera_uuid!==void 0){let i=r;g(e.camera_uuid,{instancePath:s+"/camera_uuid",parentData:e,parentDataProperty:"camera_uuid",rootData:u,dynamicAnchors:m})||(t=t===null?g.errors:t.concat(g.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.name!==void 0){let i=r;b(e.name,{instancePath:s+"/name",parentData:e,parentDataProperty:"name",rootData:u,dynamicAnchors:m})||(t=t===null?b.errors:t.concat(b.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.location_uuid!==void 0){let i=r;g(e.location_uuid,{instancePath:s+"/location_uuid",parentData:e,parentDataProperty:"location_uuid",rootData:u,dynamicAnchors:m})||(t=t===null?g.errors:t.concat(g.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.location_name!==void 0){let i=e.location_name,o=r;if(r===o)if(typeof i=="string"){if(A(i)>127)return f.errors=[{instancePath:s+"/location_name",schemaPath:"#/properties/location_name/maxLength",keyword:"maxLength",params:{limit:127},message:"must NOT have more than 127 characters"}],!1}else return f.errors=[{instancePath:s+"/location_name",schemaPath:"#/properties/location_name/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=o===r}else var n=!0;if(n){if(e.location_path!==void 0){let i=e.location_path,o=r;if(r===o)if(typeof i=="string"){if(A(i)>1023)return f.errors=[{instancePath:s+"/location_path",schemaPath:"#/properties/location_path/maxLength",keyword:"maxLength",params:{limit:1023},message:"must NOT have more than 1023 characters"}],!1}else return f.errors=[{instancePath:s+"/location_path",schemaPath:"#/properties/location_path/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=o===r}else var n=!0;if(n){if(e.enabled!==void 0){let i=r;if(typeof e.enabled!="boolean")return f.errors=[{instancePath:s+"/enabled",schemaPath:"#/properties/enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=i===r}else var n=!0;if(n){if(e.width!==void 0){let i=r;_(e.width,{instancePath:s+"/width",parentData:e,parentDataProperty:"width",rootData:u,dynamicAnchors:m})||(t=t===null?_.errors:t.concat(_.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.height!==void 0){let i=r;_(e.height,{instancePath:s+"/height",parentData:e,parentDataProperty:"height",rootData:u,dynamicAnchors:m})||(t=t===null?_.errors:t.concat(_.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.fps!==void 0){let i=r;_(e.fps,{instancePath:s+"/fps",parentData:e,parentDataProperty:"fps",rootData:u,dynamicAnchors:m})||(t=t===null?_.errors:t.concat(_.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.codec!==void 0){let i=e.codec,o=r;if(r===o)if(typeof i=="string"){if(A(i)>15)return f.errors=[{instancePath:s+"/codec",schemaPath:"#/properties/codec/maxLength",keyword:"maxLength",params:{limit:15},message:"must NOT have more than 15 characters"}],!1}else return f.errors=[{instancePath:s+"/codec",schemaPath:"#/properties/codec/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=o===r}else var n=!0;if(n){if(e.protocol!==void 0){let i=r;P(e.protocol,{instancePath:s+"/protocol",parentData:e,parentDataProperty:"protocol",rootData:u,dynamicAnchors:m})||(t=t===null?P.errors:t.concat(P.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.record!==void 0){let i=r;if(typeof e.record!="boolean")return f.errors=[{instancePath:s+"/record",schemaPath:"#/properties/record/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=i===r}else var n=!0;if(n){if(e.record_on_schedule!==void 0){let i=r;if(typeof e.record_on_schedule!="boolean")return f.errors=[{instancePath:s+"/record_on_schedule",schemaPath:"#/properties/record_on_schedule/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=i===r}else var n=!0;if(n){if(e.detection_based_recording!==void 0){let i=r;if(typeof e.detection_based_recording!="boolean")return f.errors=[{instancePath:s+"/detection_based_recording",schemaPath:"#/properties/detection_based_recording/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=i===r}else var n=!0;if(n){if(e.isOnvif!==void 0){let i=r;if(typeof e.isOnvif!="boolean")return f.errors=[{instancePath:s+"/isOnvif",schemaPath:"#/properties/isOnvif/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=i===r}else var n=!0;if(n){if(e.tags!==void 0){let i=e.tags,o=r;if(r===o)if(typeof i=="string"){if(A(i)>255)return f.errors=[{instancePath:s+"/tags",schemaPath:"#/properties/tags/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1}else return f.errors=[{instancePath:s+"/tags",schemaPath:"#/properties/tags/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=o===r}else var n=!0;if(n){if(e.status!==void 0){let i=r;w(e.status,{instancePath:s+"/status",parentData:e,parentDataProperty:"status",rootData:u,dynamicAnchors:m})||(t=t===null?w.errors:t.concat(w.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.availability!==void 0){let i=r;k(e.availability,{instancePath:s+"/availability",parentData:e,parentDataProperty:"availability",rootData:u,dynamicAnchors:m})||(t=t===null?k.errors:t.concat(k.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.first_video_at!==void 0){let i=r;h(e.first_video_at,{instancePath:s+"/first_video_at",parentData:e,parentDataProperty:"first_video_at",rootData:u,dynamicAnchors:m})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.last_frame_ts!==void 0){let i=r;h(e.last_frame_ts,{instancePath:s+"/last_frame_ts",parentData:e,parentDataProperty:"last_frame_ts",rootData:u,dynamicAnchors:m})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var n=i===r}else var n=!0;if(n){if(e.current_fps!==void 0){let i=e.current_fps,o=r;if(r===o)if(typeof i=="number"&&isFinite(i)){if(i<0||isNaN(i))return f.errors=[{instancePath:s+"/current_fps",schemaPath:"#/properties/current_fps/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1}else return f.errors=[{instancePath:s+"/current_fps",schemaPath:"#/properties/current_fps/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var n=o===r}else var n=!0;if(n){if(e.recording_active!==void 0){let i=r;if(typeof e.recording_active!="boolean")return f.errors=[{instancePath:s+"/recording_active",schemaPath:"#/properties/recording_active/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=i===r}else var n=!0;if(n){if(e.last_recording_at!==void 0){let i=r;h(e.last_recording_at,{instancePath:s+"/last_recording_at",parentData:e,parentDataProperty:"last_recording_at",rootData:u,dynamicAnchors:m})||(t=t===null?h.errors:t.concat(h.errors),r=t.length);var n=i===r}else var n=!0;if(n)if(e.can_configure!==void 0){let i=r;if(typeof e.can_configure!="boolean")return f.errors=[{instancePath:s+"/can_configure",schemaPath:"#/properties/can_configure/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=i===r}else var n=!0}}}}}}}}}}}}}}}}}}}}}}}else return f.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return f.errors=t,r===0}f.evaluated={props:{camera_uuid:!0,name:!0,location_uuid:!0,location_name:!0,location_path:!0,enabled:!0,width:!0,height:!0,fps:!0,codec:!0,protocol:!0,record:!0,record_on_schedule:!0,detection_based_recording:!0,isOnvif:!0,tags:!0,status:!0,availability:!0,first_video_at:!0,last_frame_ts:!0,current_fps:!0,recording_active:!0,last_recording_at:!0,can_configure:!0},dynamicProps:!1,dynamicItems:!1};export{M as validateStreamSummaryBase}; diff --git a/web/js/api/generated/validators/StreamSummaryLive.d.mts b/web/js/api/generated/validators/StreamSummaryLive.d.mts new file mode 100644 index 000000000..1f6da479a --- /dev/null +++ b/web/js/api/generated/validators/StreamSummaryLive.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateStreamSummaryLive } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/StreamSummaryLive.mjs b/web/js/api/generated/validators/StreamSummaryLive.mjs new file mode 100644 index 000000000..2c2dc2360 --- /dev/null +++ b/web/js/api/generated/validators/StreamSummaryLive.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var C=(e,n)=>()=>{try{return n||e((n={exports:{}}).exports,n),n.exports}catch(_){throw n=0,_}};var Y=C(I=>{"use strict";Object.defineProperty(I,"__esModule",{value:!0});function E(e){let n=e.length,_=0,y=0,u;for(;y=55296&&u<=56319&&y36)return k.errors=[{instancePath:n,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:36},message:"must NOT have more than 36 characters"}],!1;if(!U.test(e))return k.errors=[{instancePath:n,schemaPath:"#/pattern",keyword:"pattern",params:{pattern:"^(|[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})$"},message:'must match pattern "^(|[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})$"'}],!1}else return k.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return k.errors=t,r===0}k.evaluated={dynamicProps:!1,dynamicItems:!1};function x(e,{instancePath:n="",parentData:_,parentDataProperty:y,rootData:u=e,dynamicAnchors:f={}}={}){let t=null,r=0,o=x.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(typeof e=="string"){if(F(e)>255)return x.errors=[{instancePath:n,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1;if(F(e)<1)return x.errors=[{instancePath:n,schemaPath:"#/minLength",keyword:"minLength",params:{limit:1},message:"must NOT have fewer than 1 characters"}],!1;if(new TextEncoder().encode(e).byteLength>255)return x.errors=[{instancePath:n,schemaPath:"#/x-lightnvr-max-bytes",keyword:"x-lightnvr-max-bytes",params:{},message:"must fit the UTF-8 byte limit"}],!1}else return x.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return x.errors=t,r===0}x.evaluated={dynamicProps:!1,dynamicItems:!1};function w(e,{instancePath:n="",parentData:_,parentDataProperty:y,rootData:u=e,dynamicAnchors:f={}}={}){let t=null,r=0,o=w.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),!(typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)))return w.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;if(r===0&&typeof e=="number"&&isFinite(e)){if(e>2147483647||isNaN(e))return w.errors=[{instancePath:n,schemaPath:"#/maximum",keyword:"maximum",params:{comparison:"<=",limit:2147483647},message:"must be <= 2147483647"}],!1;if(e<-2147483648||isNaN(e))return w.errors=[{instancePath:n,schemaPath:"#/minimum",keyword:"minimum",params:{comparison:">=",limit:-2147483648},message:"must be >= -2147483648"}],!1}return w.errors=t,r===0}w.evaluated={dynamicProps:!1,dynamicItems:!1};var Z={type:"integer",enum:[0,1],$id:"urn:lightnvr:contract:StreamProtocol"};function D(e,{instancePath:n="",parentData:_,parentDataProperty:y,rootData:u=e,dynamicAnchors:f={}}={}){let t=null,r=0,o=D.evaluated;return o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)?e===0||e===1?(D.errors=t,r===0):(D.errors=[{instancePath:n,schemaPath:"#/enum",keyword:"enum",params:{allowedValues:Z.enum},message:"must be equal to one of the allowed values"}],!1):(D.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1)}D.evaluated={dynamicProps:!1,dynamicItems:!1};var R={type:"string",enum:["Running","Degraded","Error","Stopped","Unknown"],$id:"urn:lightnvr:contract:SummaryStatus"};function A(e,{instancePath:n="",parentData:_,parentDataProperty:y,rootData:u=e,dynamicAnchors:f={}}={}){let t=null,r=0,o=A.evaluated;return o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e!="string"?(A.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1):e==="Running"||e==="Degraded"||e==="Error"||e==="Stopped"||e==="Unknown"?(A.errors=t,r===0):(A.errors=[{instancePath:n,schemaPath:"#/enum",keyword:"enum",params:{allowedValues:R.enum},message:"must be equal to one of the allowed values"}],!1)}A.evaluated={dynamicProps:!1,dynamicItems:!1};var B={type:"string",enum:["live","offline","never_connected","disabled"],$id:"urn:lightnvr:contract:Availability"};function O(e,{instancePath:n="",parentData:_,parentDataProperty:y,rootData:u=e,dynamicAnchors:f={}}={}){let t=null,r=0,o=O.evaluated;return o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e!="string"?(O.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1):e==="live"||e==="offline"||e==="never_connected"||e==="disabled"?(O.errors=t,r===0):(O.errors=[{instancePath:n,schemaPath:"#/enum",keyword:"enum",params:{allowedValues:B.enum},message:"must be equal to one of the allowed values"}],!1)}O.evaluated={dynamicProps:!1,dynamicItems:!1};function P(e,{instancePath:n="",parentData:_,parentDataProperty:y,rootData:u=e,dynamicAnchors:f={}}={}){let t=null,r=0,o=P.evaluated;return o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)?r===0&&typeof e=="number"&&isFinite(e)&&(e<0||isNaN(e))?(P.errors=[{instancePath:n,schemaPath:"#/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1):(P.errors=t,r===0):(P.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1)}P.evaluated={dynamicProps:!1,dynamicItems:!1};function p(e,{instancePath:n="",parentData:_,parentDataProperty:y,rootData:u=e,dynamicAnchors:f={}}={}){let t=null,r=0,o=p.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),$(e,{instancePath:n,parentData:_,parentDataProperty:y,rootData:u,dynamicAnchors:f})||(t=t===null?$.errors:t.concat($.errors),r=t.length),r===0)if(e&&typeof e=="object"&&!Array.isArray(e)){let l;if(e.camera_uuid===void 0&&(l="camera_uuid")||e.name===void 0&&(l="name")||e.location_uuid===void 0&&(l="location_uuid")||e.location_name===void 0&&(l="location_name")||e.location_path===void 0&&(l="location_path")||e.enabled===void 0&&(l="enabled")||e.width===void 0&&(l="width")||e.height===void 0&&(l="height")||e.fps===void 0&&(l="fps")||e.codec===void 0&&(l="codec")||e.protocol===void 0&&(l="protocol")||e.record===void 0&&(l="record")||e.record_on_schedule===void 0&&(l="record_on_schedule")||e.detection_based_recording===void 0&&(l="detection_based_recording")||e.isOnvif===void 0&&(l="isOnvif")||e.tags===void 0&&(l="tags")||e.status===void 0&&(l="status")||e.availability===void 0&&(l="availability")||e.first_video_at===void 0&&(l="first_video_at")||e.last_frame_ts===void 0&&(l="last_frame_ts")||e.current_fps===void 0&&(l="current_fps")||e.recording_active===void 0&&(l="recording_active")||e.last_recording_at===void 0&&(l="last_recording_at")||e.can_configure===void 0&&(l="can_configure"))return p.errors=[{instancePath:n,schemaPath:"#/required",keyword:"required",params:{missingProperty:l},message:"must have required property '"+l+"'"}],!1;if(e.camera_uuid!==void 0){let s=r;k(e.camera_uuid,{instancePath:n+"/camera_uuid",parentData:e,parentDataProperty:"camera_uuid",rootData:u,dynamicAnchors:f})||(t=t===null?k.errors:t.concat(k.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.name!==void 0){let s=r;x(e.name,{instancePath:n+"/name",parentData:e,parentDataProperty:"name",rootData:u,dynamicAnchors:f})||(t=t===null?x.errors:t.concat(x.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.location_uuid!==void 0){let s=r;k(e.location_uuid,{instancePath:n+"/location_uuid",parentData:e,parentDataProperty:"location_uuid",rootData:u,dynamicAnchors:f})||(t=t===null?k.errors:t.concat(k.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.location_name!==void 0){let s=e.location_name,a=r;if(r===a)if(typeof s=="string"){if(F(s)>127)return p.errors=[{instancePath:n+"/location_name",schemaPath:"#/properties/location_name/maxLength",keyword:"maxLength",params:{limit:127},message:"must NOT have more than 127 characters"}],!1}else return p.errors=[{instancePath:n+"/location_name",schemaPath:"#/properties/location_name/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=a===r}else var i=!0;if(i){if(e.location_path!==void 0){let s=e.location_path,a=r;if(r===a)if(typeof s=="string"){if(F(s)>1023)return p.errors=[{instancePath:n+"/location_path",schemaPath:"#/properties/location_path/maxLength",keyword:"maxLength",params:{limit:1023},message:"must NOT have more than 1023 characters"}],!1}else return p.errors=[{instancePath:n+"/location_path",schemaPath:"#/properties/location_path/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=a===r}else var i=!0;if(i){if(e.enabled!==void 0){let s=r;if(typeof e.enabled!="boolean")return p.errors=[{instancePath:n+"/enabled",schemaPath:"#/properties/enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.width!==void 0){let s=r;w(e.width,{instancePath:n+"/width",parentData:e,parentDataProperty:"width",rootData:u,dynamicAnchors:f})||(t=t===null?w.errors:t.concat(w.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.height!==void 0){let s=r;w(e.height,{instancePath:n+"/height",parentData:e,parentDataProperty:"height",rootData:u,dynamicAnchors:f})||(t=t===null?w.errors:t.concat(w.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.fps!==void 0){let s=r;w(e.fps,{instancePath:n+"/fps",parentData:e,parentDataProperty:"fps",rootData:u,dynamicAnchors:f})||(t=t===null?w.errors:t.concat(w.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.codec!==void 0){let s=e.codec,a=r;if(r===a)if(typeof s=="string"){if(F(s)>15)return p.errors=[{instancePath:n+"/codec",schemaPath:"#/properties/codec/maxLength",keyword:"maxLength",params:{limit:15},message:"must NOT have more than 15 characters"}],!1}else return p.errors=[{instancePath:n+"/codec",schemaPath:"#/properties/codec/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=a===r}else var i=!0;if(i){if(e.protocol!==void 0){let s=r;D(e.protocol,{instancePath:n+"/protocol",parentData:e,parentDataProperty:"protocol",rootData:u,dynamicAnchors:f})||(t=t===null?D.errors:t.concat(D.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.record!==void 0){let s=r;if(typeof e.record!="boolean")return p.errors=[{instancePath:n+"/record",schemaPath:"#/properties/record/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.record_on_schedule!==void 0){let s=r;if(typeof e.record_on_schedule!="boolean")return p.errors=[{instancePath:n+"/record_on_schedule",schemaPath:"#/properties/record_on_schedule/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.detection_based_recording!==void 0){let s=r;if(typeof e.detection_based_recording!="boolean")return p.errors=[{instancePath:n+"/detection_based_recording",schemaPath:"#/properties/detection_based_recording/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.isOnvif!==void 0){let s=r;if(typeof e.isOnvif!="boolean")return p.errors=[{instancePath:n+"/isOnvif",schemaPath:"#/properties/isOnvif/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.tags!==void 0){let s=e.tags,a=r;if(r===a)if(typeof s=="string"){if(F(s)>255)return p.errors=[{instancePath:n+"/tags",schemaPath:"#/properties/tags/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1}else return p.errors=[{instancePath:n+"/tags",schemaPath:"#/properties/tags/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var i=a===r}else var i=!0;if(i){if(e.status!==void 0){let s=r;A(e.status,{instancePath:n+"/status",parentData:e,parentDataProperty:"status",rootData:u,dynamicAnchors:f})||(t=t===null?A.errors:t.concat(A.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.availability!==void 0){let s=r;O(e.availability,{instancePath:n+"/availability",parentData:e,parentDataProperty:"availability",rootData:u,dynamicAnchors:f})||(t=t===null?O.errors:t.concat(O.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.first_video_at!==void 0){let s=r;P(e.first_video_at,{instancePath:n+"/first_video_at",parentData:e,parentDataProperty:"first_video_at",rootData:u,dynamicAnchors:f})||(t=t===null?P.errors:t.concat(P.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.last_frame_ts!==void 0){let s=r;P(e.last_frame_ts,{instancePath:n+"/last_frame_ts",parentData:e,parentDataProperty:"last_frame_ts",rootData:u,dynamicAnchors:f})||(t=t===null?P.errors:t.concat(P.errors),r=t.length);var i=s===r}else var i=!0;if(i){if(e.current_fps!==void 0){let s=e.current_fps,a=r;if(r===a)if(typeof s=="number"&&isFinite(s)){if(s<0||isNaN(s))return p.errors=[{instancePath:n+"/current_fps",schemaPath:"#/properties/current_fps/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1}else return p.errors=[{instancePath:n+"/current_fps",schemaPath:"#/properties/current_fps/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var i=a===r}else var i=!0;if(i){if(e.recording_active!==void 0){let s=r;if(typeof e.recording_active!="boolean")return p.errors=[{instancePath:n+"/recording_active",schemaPath:"#/properties/recording_active/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0;if(i){if(e.last_recording_at!==void 0){let s=r;P(e.last_recording_at,{instancePath:n+"/last_recording_at",parentData:e,parentDataProperty:"last_recording_at",rootData:u,dynamicAnchors:f})||(t=t===null?P.errors:t.concat(P.errors),r=t.length);var i=s===r}else var i=!0;if(i)if(e.can_configure!==void 0){let s=r;if(typeof e.can_configure!="boolean")return p.errors=[{instancePath:n+"/can_configure",schemaPath:"#/properties/can_configure/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var i=s===r}else var i=!0}}}}}}}}}}}}}}}}}}}}}}}else return p.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return p.errors=t,r===0}p.evaluated={props:{camera_uuid:!0,name:!0,location_uuid:!0,location_name:!0,location_path:!0,enabled:!0,width:!0,height:!0,fps:!0,codec:!0,protocol:!0,record:!0,record_on_schedule:!0,detection_based_recording:!0,isOnvif:!0,tags:!0,status:!0,availability:!0,first_video_at:!0,last_frame_ts:!0,current_fps:!0,recording_active:!0,last_recording_at:!0,can_configure:!0},dynamicProps:!1,dynamicItems:!1};var J={type:"string",enum:["auto","webrtc_only","mse_only","hls_only","webrtc_then_mse","mse_then_hls"],$id:"urn:lightnvr:contract:PlaybackTransport"};function S(e,{instancePath:n="",parentData:_,parentDataProperty:y,rootData:u=e,dynamicAnchors:f={}}={}){let t=null,r=0,o=S.evaluated;return o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e!="string"?(S.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1):e==="auto"||e==="webrtc_only"||e==="mse_only"||e==="hls_only"||e==="webrtc_then_mse"||e==="mse_then_hls"?(S.errors=t,r===0):(S.errors=[{instancePath:n,schemaPath:"#/enum",keyword:"enum",params:{allowedValues:J.enum},message:"must be equal to one of the allowed values"}],!1)}S.evaluated={dynamicProps:!1,dynamicItems:!1};function m(e,{instancePath:n="",parentData:_,parentDataProperty:y,rootData:u=e,dynamicAnchors:f={}}={}){let t=null,r=0,o=m.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(e&&typeof e=="object"&&!Array.isArray(e)){let l;if(e.version===void 0&&(l="version")||e.projection===void 0&&(l="projection")||e.mount===void 0&&(l="mount")||e.centerX===void 0&&(l="centerX")||e.centerY===void 0&&(l="centerY")||e.radius===void 0&&(l="radius")||e.fov===void 0&&(l="fov")||e.rotation===void 0&&(l="rotation")||e.defaultYaw===void 0&&(l="defaultYaw")||e.defaultTilt===void 0&&(l="defaultTilt")||e.defaultViewFov===void 0&&(l="defaultViewFov"))return m.errors=[{instancePath:n,schemaPath:"#/required",keyword:"required",params:{missingProperty:l},message:"must have required property '"+l+"'"}],!1;if(e.version!==void 0){let s=r;if(e.version!==1)return m.errors=[{instancePath:n+"/version",schemaPath:"#/properties/version/const",keyword:"const",params:{allowedValue:1},message:"must be equal to constant"}],!1;var i=s===r}else var i=!0;if(i){if(e.projection!==void 0){let s=r;if(e.projection!=="equidistant")return m.errors=[{instancePath:n+"/projection",schemaPath:"#/properties/projection/const",keyword:"const",params:{allowedValue:"equidistant"},message:"must be equal to constant"}],!1;var i=s===r}else var i=!0;if(i){if(e.mount!==void 0){let s=r;if(e.mount!=="ceiling")return m.errors=[{instancePath:n+"/mount",schemaPath:"#/properties/mount/const",keyword:"const",params:{allowedValue:"ceiling"},message:"must be equal to constant"}],!1;var i=s===r}else var i=!0;if(i){if(e.centerX!==void 0){let s=e.centerX,a=r;if(r===a)if(typeof s=="number"&&isFinite(s)){if(s>1||isNaN(s))return m.errors=[{instancePath:n+"/centerX",schemaPath:"#/properties/centerX/maximum",keyword:"maximum",params:{comparison:"<=",limit:1},message:"must be <= 1"}],!1;if(s<0||isNaN(s))return m.errors=[{instancePath:n+"/centerX",schemaPath:"#/properties/centerX/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1}else return m.errors=[{instancePath:n+"/centerX",schemaPath:"#/properties/centerX/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var i=a===r}else var i=!0;if(i){if(e.centerY!==void 0){let s=e.centerY,a=r;if(r===a)if(typeof s=="number"&&isFinite(s)){if(s>1||isNaN(s))return m.errors=[{instancePath:n+"/centerY",schemaPath:"#/properties/centerY/maximum",keyword:"maximum",params:{comparison:"<=",limit:1},message:"must be <= 1"}],!1;if(s<0||isNaN(s))return m.errors=[{instancePath:n+"/centerY",schemaPath:"#/properties/centerY/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1}else return m.errors=[{instancePath:n+"/centerY",schemaPath:"#/properties/centerY/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var i=a===r}else var i=!0;if(i){if(e.radius!==void 0){let s=e.radius,a=r;if(r===a)if(typeof s=="number"&&isFinite(s)){if(s>1||isNaN(s))return m.errors=[{instancePath:n+"/radius",schemaPath:"#/properties/radius/maximum",keyword:"maximum",params:{comparison:"<=",limit:1},message:"must be <= 1"}],!1;if(s<.05||isNaN(s))return m.errors=[{instancePath:n+"/radius",schemaPath:"#/properties/radius/minimum",keyword:"minimum",params:{comparison:">=",limit:.05},message:"must be >= 0.05"}],!1}else return m.errors=[{instancePath:n+"/radius",schemaPath:"#/properties/radius/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var i=a===r}else var i=!0;if(i){if(e.fov!==void 0){let s=e.fov,a=r;if(r===a)if(typeof s=="number"&&isFinite(s)){if(s>360||isNaN(s))return m.errors=[{instancePath:n+"/fov",schemaPath:"#/properties/fov/maximum",keyword:"maximum",params:{comparison:"<=",limit:360},message:"must be <= 360"}],!1;if(s<160||isNaN(s))return m.errors=[{instancePath:n+"/fov",schemaPath:"#/properties/fov/minimum",keyword:"minimum",params:{comparison:">=",limit:160},message:"must be >= 160"}],!1}else return m.errors=[{instancePath:n+"/fov",schemaPath:"#/properties/fov/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var i=a===r}else var i=!0;if(i){if(e.rotation!==void 0){let s=e.rotation,a=r;if(r===a)if(typeof s=="number"&&isFinite(s)){if(s>360||isNaN(s))return m.errors=[{instancePath:n+"/rotation",schemaPath:"#/properties/rotation/maximum",keyword:"maximum",params:{comparison:"<=",limit:360},message:"must be <= 360"}],!1;if(s<-360||isNaN(s))return m.errors=[{instancePath:n+"/rotation",schemaPath:"#/properties/rotation/minimum",keyword:"minimum",params:{comparison:">=",limit:-360},message:"must be >= -360"}],!1}else return m.errors=[{instancePath:n+"/rotation",schemaPath:"#/properties/rotation/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var i=a===r}else var i=!0;if(i){if(e.defaultYaw!==void 0){let s=e.defaultYaw,a=r;if(r===a)if(typeof s=="number"&&isFinite(s)){if(s>360||isNaN(s))return m.errors=[{instancePath:n+"/defaultYaw",schemaPath:"#/properties/defaultYaw/maximum",keyword:"maximum",params:{comparison:"<=",limit:360},message:"must be <= 360"}],!1;if(s<-360||isNaN(s))return m.errors=[{instancePath:n+"/defaultYaw",schemaPath:"#/properties/defaultYaw/minimum",keyword:"minimum",params:{comparison:">=",limit:-360},message:"must be >= -360"}],!1}else return m.errors=[{instancePath:n+"/defaultYaw",schemaPath:"#/properties/defaultYaw/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var i=a===r}else var i=!0;if(i){if(e.defaultTilt!==void 0){let s=e.defaultTilt,a=r;if(r===a)if(typeof s=="number"&&isFinite(s)){if(s>30||isNaN(s))return m.errors=[{instancePath:n+"/defaultTilt",schemaPath:"#/properties/defaultTilt/maximum",keyword:"maximum",params:{comparison:"<=",limit:30},message:"must be <= 30"}],!1;if(s<-90||isNaN(s))return m.errors=[{instancePath:n+"/defaultTilt",schemaPath:"#/properties/defaultTilt/minimum",keyword:"minimum",params:{comparison:">=",limit:-90},message:"must be >= -90"}],!1}else return m.errors=[{instancePath:n+"/defaultTilt",schemaPath:"#/properties/defaultTilt/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var i=a===r}else var i=!0;if(i)if(e.defaultViewFov!==void 0){let s=e.defaultViewFov,a=r;if(r===a)if(typeof s=="number"&&isFinite(s)){if(s>120||isNaN(s))return m.errors=[{instancePath:n+"/defaultViewFov",schemaPath:"#/properties/defaultViewFov/maximum",keyword:"maximum",params:{comparison:"<=",limit:120},message:"must be <= 120"}],!1;if(s<20||isNaN(s))return m.errors=[{instancePath:n+"/defaultViewFov",schemaPath:"#/properties/defaultViewFov/minimum",keyword:"minimum",params:{comparison:">=",limit:20},message:"must be >= 20"}],!1}else return m.errors=[{instancePath:n+"/defaultViewFov",schemaPath:"#/properties/defaultViewFov/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var i=a===r}else var i=!0}}}}}}}}}}else return m.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return m.errors=t,r===0}m.evaluated={props:{version:!0,projection:!0,mount:!0,centerX:!0,centerY:!0,radius:!0,fov:!0,rotation:!0,defaultYaw:!0,defaultTilt:!0,defaultViewFov:!0},dynamicProps:!1,dynamicItems:!1};function N(e,{instancePath:n="",parentData:_,parentDataProperty:y,rootData:u=e,dynamicAnchors:f={}}={}){let t=null,r=0,o=N.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(typeof e=="string"){if(F(e)>1023)return N.errors=[{instancePath:n,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:1023},message:"must NOT have more than 1023 characters"}],!1;if(new TextEncoder().encode(e).byteLength>1023)return N.errors=[{instancePath:n,schemaPath:"#/x-lightnvr-max-bytes",keyword:"x-lightnvr-max-bytes",params:{},message:"must fit the UTF-8 byte limit"}],!1;if(e!==""){let l,s=!0;try{l=JSON.parse(e)}catch{s=!1}if(s){let a=r;m(l,{instancePath:n,parentData:e,parentDataProperty:y,rootData:u,dynamicAnchors:f})||(t=t===null?m.errors:t.concat(m.errors),r=t.length);var i=a===r;s=i}if(!s)return N.errors=[{instancePath:n,schemaPath:"#/contentSchema",keyword:"contentSchema",params:{},message:"must contain valid JSON matching contentSchema (or be empty)"}],!1}}else return N.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return N.errors=t,r===0}N.evaluated={dynamicProps:!1,dynamicItems:!1};function g(e,{instancePath:n="",parentData:_,parentDataProperty:y,rootData:u=e,dynamicAnchors:f={}}={}){let t=null,r=0,o=g.evaluated;o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0);let i=r;p(e,{instancePath:n,parentData:_,parentDataProperty:y,rootData:u,dynamicAnchors:f})||(t=t===null?p.errors:t.concat(p.errors),r=t.length);var l=i===r;if(l){let q=r,j=r,T=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let h;if(e.admin_url===void 0&&(h="admin_url")){let c={};t===null?t=[c]:t.push(c),r++}}var s=T===r;if(s)return g.errors=[{instancePath:n,schemaPath:"#/allOf/1/not",keyword:"not",params:{},message:"must NOT be valid"}],!1;if(r=j,t!==null&&(j?t.length=j:t=null),r===q)if(e&&typeof e=="object"&&!Array.isArray(e)){let h;if(e.streaming_enabled===void 0&&(h="streaming_enabled")||e.playback_transport===void 0&&(h="playback_transport")||e.eptz_config===void 0&&(h="eptz_config")||e.detection_model===void 0&&(h="detection_model")||e.privacy_mode===void 0&&(h="privacy_mode")||e.can_control_privacy===void 0&&(h="can_control_privacy")||e.has_sub_stream===void 0&&(h="has_sub_stream")||e.ptz_enabled===void 0&&(h="ptz_enabled")||e.backchannel_enabled===void 0&&(h="backchannel_enabled")||e.go2rtc_hls_available===void 0&&(h="go2rtc_hls_available"))return g.errors=[{instancePath:n,schemaPath:"#/allOf/1/required",keyword:"required",params:{missingProperty:h},message:"must have required property '"+h+"'"}],!1;if(e.streaming_enabled!==void 0){let c=r;if(typeof e.streaming_enabled!="boolean")return g.errors=[{instancePath:n+"/streaming_enabled",schemaPath:"#/allOf/1/properties/streaming_enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var a=c===r}else var a=!0;if(a){if(e.playback_transport!==void 0){let c=r;S(e.playback_transport,{instancePath:n+"/playback_transport",parentData:e,parentDataProperty:"playback_transport",rootData:u,dynamicAnchors:f})||(t=t===null?S.errors:t.concat(S.errors),r=t.length);var a=c===r}else var a=!0;if(a){if(e.eptz_config!==void 0){let c=r;N(e.eptz_config,{instancePath:n+"/eptz_config",parentData:e,parentDataProperty:"eptz_config",rootData:u,dynamicAnchors:f})||(t=t===null?N.errors:t.concat(N.errors),r=t.length);var a=c===r}else var a=!0;if(a){if(e.detection_model!==void 0){let c=e.detection_model,L=r;if(r===L)if(typeof c=="string"){if(F(c)>511)return g.errors=[{instancePath:n+"/detection_model",schemaPath:"#/allOf/1/properties/detection_model/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return g.errors=[{instancePath:n+"/detection_model",schemaPath:"#/allOf/1/properties/detection_model/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var a=L===r}else var a=!0;if(a){if(e.privacy_mode!==void 0){let c=r;if(typeof e.privacy_mode!="boolean")return g.errors=[{instancePath:n+"/privacy_mode",schemaPath:"#/allOf/1/properties/privacy_mode/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var a=c===r}else var a=!0;if(a){if(e.can_control_privacy!==void 0){let c=r;if(typeof e.can_control_privacy!="boolean")return g.errors=[{instancePath:n+"/can_control_privacy",schemaPath:"#/allOf/1/properties/can_control_privacy/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var a=c===r}else var a=!0;if(a){if(e.has_sub_stream!==void 0){let c=r;if(typeof e.has_sub_stream!="boolean")return g.errors=[{instancePath:n+"/has_sub_stream",schemaPath:"#/allOf/1/properties/has_sub_stream/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var a=c===r}else var a=!0;if(a){if(e.ptz_enabled!==void 0){let c=r;if(typeof e.ptz_enabled!="boolean")return g.errors=[{instancePath:n+"/ptz_enabled",schemaPath:"#/allOf/1/properties/ptz_enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var a=c===r}else var a=!0;if(a){if(e.backchannel_enabled!==void 0){let c=r;if(typeof e.backchannel_enabled!="boolean")return g.errors=[{instancePath:n+"/backchannel_enabled",schemaPath:"#/allOf/1/properties/backchannel_enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var a=c===r}else var a=!0;if(a)if(e.go2rtc_hls_available!==void 0){let c=r;if(typeof e.go2rtc_hls_available!="boolean")return g.errors=[{instancePath:n+"/go2rtc_hls_available",schemaPath:"#/allOf/1/properties/go2rtc_hls_available/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var a=c===r}else var a=!0}}}}}}}}}else return g.errors=[{instancePath:n,schemaPath:"#/allOf/1/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;var l=q===r}return g.errors=t,r===0}g.evaluated={props:{streaming_enabled:!0,playback_transport:!0,eptz_config:!0,detection_model:!0,privacy_mode:!0,can_control_privacy:!0,has_sub_stream:!0,ptz_enabled:!0,backchannel_enabled:!0,go2rtc_hls_available:!0,camera_uuid:!0,name:!0,location_uuid:!0,location_name:!0,location_path:!0,enabled:!0,width:!0,height:!0,fps:!0,codec:!0,protocol:!0,record:!0,record_on_schedule:!0,detection_based_recording:!0,isOnvif:!0,tags:!0,status:!0,availability:!0,first_video_at:!0,last_frame_ts:!0,current_fps:!0,recording_active:!0,last_recording_at:!0,can_configure:!0},dynamicProps:!1,dynamicItems:!1};export{G as validateStreamSummaryLive}; diff --git a/web/js/api/generated/validators/StreamSummaryLpr.d.mts b/web/js/api/generated/validators/StreamSummaryLpr.d.mts new file mode 100644 index 000000000..2e2fc59a2 --- /dev/null +++ b/web/js/api/generated/validators/StreamSummaryLpr.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateStreamSummaryLpr } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/StreamSummaryLpr.mjs b/web/js/api/generated/validators/StreamSummaryLpr.mjs new file mode 100644 index 000000000..4ddb5657b --- /dev/null +++ b/web/js/api/generated/validators/StreamSummaryLpr.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var F=(e,r)=>()=>{try{return r||e((r={exports:{}}).exports,r),r.exports}catch(u){throw r=0,u}};var v=F(h=>{"use strict";Object.defineProperty(h,"__esModule",{value:!0});function g(e){let r=e.length,u=0,o=0,n;for(;o=55296&&n<=56319&&o36)return m.errors=[{instancePath:r,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:36},message:"must NOT have more than 36 characters"}],!1;if(!P.test(e))return m.errors=[{instancePath:r,schemaPath:"#/pattern",keyword:"pattern",params:{pattern:"^(|[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})$"},message:'must match pattern "^(|[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})$"'}],!1}else return m.errors=[{instancePath:r,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return m.errors=s,a===0}m.evaluated={dynamicProps:!1,dynamicItems:!1};function i(e,{instancePath:r="",parentData:u,parentDataProperty:o,rootData:n=e,dynamicAnchors:c={}}={}){let s=null,a=0,t=i.evaluated;if(t.dynamicProps&&(t.props=void 0),t.dynamicItems&&(t.items=void 0),a===0)if(typeof e=="string"){if(y(e)>255)return i.errors=[{instancePath:r,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1;if(y(e)<1)return i.errors=[{instancePath:r,schemaPath:"#/minLength",keyword:"minLength",params:{limit:1},message:"must NOT have fewer than 1 characters"}],!1;if(new TextEncoder().encode(e).byteLength>255)return i.errors=[{instancePath:r,schemaPath:"#/x-lightnvr-max-bytes",keyword:"x-lightnvr-max-bytes",params:{},message:"must fit the UTF-8 byte limit"}],!1}else return i.errors=[{instancePath:r,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return i.errors=s,a===0}i.evaluated={dynamicProps:!1,dynamicItems:!1};function l(e,{instancePath:r="",parentData:u,parentDataProperty:o,rootData:n=e,dynamicAnchors:c={}}={}){let s=null,a=0,t=l.evaluated;if(t.dynamicProps&&(t.props=void 0),t.dynamicItems&&(t.items=void 0),a===0)if(e&&typeof e=="object"&&!Array.isArray(e)){let p;if(e.camera_uuid===void 0&&(p="camera_uuid")||e.name===void 0&&(p="name"))return l.errors=[{instancePath:r,schemaPath:"#/required",keyword:"required",params:{missingProperty:p},message:"must have required property '"+p+"'"}],!1;{let A=a;for(let f in e)if(!(f==="camera_uuid"||f==="name")){return l.errors=[{instancePath:r,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty:f},message:"must NOT have additional properties"}],!1;break}if(A===a){if(e.camera_uuid!==void 0){let f=a;m(e.camera_uuid,{instancePath:r+"/camera_uuid",parentData:e,parentDataProperty:"camera_uuid",rootData:n,dynamicAnchors:c})||(s=s===null?m.errors:s.concat(m.errors),a=s.length);var d=f===a}else var d=!0;if(d)if(e.name!==void 0){let f=a;i(e.name,{instancePath:r+"/name",parentData:e,parentDataProperty:"name",rootData:n,dynamicAnchors:c})||(s=s===null?i.errors:s.concat(i.errors),a=s.length);var d=f===a}else var d=!0}}}else return l.errors=[{instancePath:r,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return l.errors=s,a===0}l.evaluated={props:!0,dynamicProps:!1,dynamicItems:!1};export{w as validateStreamSummaryLpr}; diff --git a/web/js/api/generated/validators/StreamSummaryPageAdmin.d.mts b/web/js/api/generated/validators/StreamSummaryPageAdmin.d.mts new file mode 100644 index 000000000..70ae3d53f --- /dev/null +++ b/web/js/api/generated/validators/StreamSummaryPageAdmin.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateStreamSummaryPageAdmin } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/StreamSummaryPageAdmin.mjs b/web/js/api/generated/validators/StreamSummaryPageAdmin.mjs new file mode 100644 index 000000000..3038df5c3 --- /dev/null +++ b/web/js/api/generated/validators/StreamSummaryPageAdmin.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var Z=(e,i)=>()=>{try{return i||e((i={exports:{}}).exports,i),i.exports}catch(d){throw i=0,d}};var U=Z(z=>{"use strict";Object.defineProperty(z,"__esModule",{value:!0});function T(e){let i=e.length,d=0,p=0,u;for(;p=55296&&u<=56319&&p=",limit:0},message:"must be >= 0"}],!1;var n=a===r}else var n=!0;if(n){if(e.page!==void 0){let s=e.page,a=r;if(!(typeof s=="number"&&!(s%1)&&!isNaN(s)&&isFinite(s)))return g.errors=[{instancePath:i+"/page",schemaPath:"#/properties/page/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;if(r===a&&typeof s=="number"&&isFinite(s)){if(s>1e6||isNaN(s))return g.errors=[{instancePath:i+"/page",schemaPath:"#/properties/page/maximum",keyword:"maximum",params:{comparison:"<=",limit:1e6},message:"must be <= 1000000"}],!1;if(s<1||isNaN(s))return g.errors=[{instancePath:i+"/page",schemaPath:"#/properties/page/minimum",keyword:"minimum",params:{comparison:">=",limit:1},message:"must be >= 1"}],!1}var n=a===r}else var n=!0;if(n){if(e.page_size!==void 0){let s=e.page_size,a=r;if(!(typeof s=="number"&&!(s%1)&&!isNaN(s)&&isFinite(s)))return g.errors=[{instancePath:i+"/page_size",schemaPath:"#/properties/page_size/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;if(r===a&&typeof s=="number"&&isFinite(s)){if(s>100||isNaN(s))return g.errors=[{instancePath:i+"/page_size",schemaPath:"#/properties/page_size/maximum",keyword:"maximum",params:{comparison:"<=",limit:100},message:"must be <= 100"}],!1;if(s<1||isNaN(s))return g.errors=[{instancePath:i+"/page_size",schemaPath:"#/properties/page_size/minimum",keyword:"minimum",params:{comparison:">=",limit:1},message:"must be >= 1"}],!1}var n=a===r}else var n=!0;if(n)if(e.total_pages!==void 0){let s=e.total_pages,a=r;if(!(typeof s=="number"&&!(s%1)&&!isNaN(s)&&isFinite(s)))return g.errors=[{instancePath:i+"/total_pages",schemaPath:"#/properties/total_pages/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;if(r===a&&typeof s=="number"&&isFinite(s)&&(s<0||isNaN(s)))return g.errors=[{instancePath:i+"/total_pages",schemaPath:"#/properties/total_pages/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1;var n=a===r}else var n=!0}}}else return g.errors=[{instancePath:i,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return g.errors=t,r===0}g.evaluated={props:{total:!0,page:!0,page_size:!0,total_pages:!0},dynamicProps:!1,dynamicItems:!1};function j(e,{instancePath:i="",parentData:d,parentDataProperty:p,rootData:u=e,dynamicAnchors:m={}}={}){let t=null,r=0,o=j.evaluated;o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0);let n=r,l=r,s=r,a=!1,q=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let y;if(e.url===void 0&&(y="url")){let c={};t===null?t=[c]:t.push(c),r++}}var h=q===r;a=a||h;let D=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let y;if(e.onvif_username===void 0&&(y="onvif_username")){let c={};t===null?t=[c]:t.push(c),r++}}var h=D===r;a=a||h;let w=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let y;if(e.onvif_password===void 0&&(y="onvif_password")){let c={};t===null?t=[c]:t.push(c),r++}}var h=w===r;a=a||h;let L=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let y;if(e.sub_stream_url===void 0&&(y="sub_stream_url")){let c={};t===null?t=[c]:t.push(c),r++}}var h=L===r;a=a||h;let E=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let y;if(e.detection_url===void 0&&(y="detection_url")){let c={};t===null?t=[c]:t.push(c),r++}}var h=E===r;a=a||h;let _=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let y;if(e.publish_url===void 0&&(y="publish_url")){let c={};t===null?t=[c]:t.push(c),r++}}var h=_===r;a=a||h;let k=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let y;if(e.go2rtc_source_override===void 0&&(y="go2rtc_source_override")){let c={};t===null?t=[c]:t.push(c),r++}}var h=k===r;a=a||h;let C=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let y;if(e.detection_api_url===void 0&&(y="detection_api_url")){let c={};t===null?t=[c]:t.push(c),r++}}var h=C===r;if(a=a||h,a)r=s,t!==null&&(s?t.length=s:t=null);else{let y={};t===null?t=[y]:t.push(y),r++}var V=l===r;return V?(j.errors=[{instancePath:i,schemaPath:"#/not",keyword:"not",params:{},message:"must NOT be valid"}],!1):(r=n,t!==null&&(n?t.length=n:t=null),j.errors=t,r===0)}j.evaluated={dynamicProps:!1,dynamicItems:!1};var F=U().default,K=new RegExp("^(|[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})$","u");function P(e,{instancePath:i="",parentData:d,parentDataProperty:p,rootData:u=e,dynamicAnchors:m={}}={}){let t=null,r=0,o=P.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(typeof e=="string"){if(F(e)>36)return P.errors=[{instancePath:i,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:36},message:"must NOT have more than 36 characters"}],!1;if(!K.test(e))return P.errors=[{instancePath:i,schemaPath:"#/pattern",keyword:"pattern",params:{pattern:"^(|[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})$"},message:'must match pattern "^(|[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})$"'}],!1}else return P.errors=[{instancePath:i,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return P.errors=t,r===0}P.evaluated={dynamicProps:!1,dynamicItems:!1};function A(e,{instancePath:i="",parentData:d,parentDataProperty:p,rootData:u=e,dynamicAnchors:m={}}={}){let t=null,r=0,o=A.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(typeof e=="string"){if(F(e)>255)return A.errors=[{instancePath:i,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1;if(F(e)<1)return A.errors=[{instancePath:i,schemaPath:"#/minLength",keyword:"minLength",params:{limit:1},message:"must NOT have fewer than 1 characters"}],!1;if(new TextEncoder().encode(e).byteLength>255)return A.errors=[{instancePath:i,schemaPath:"#/x-lightnvr-max-bytes",keyword:"x-lightnvr-max-bytes",params:{},message:"must fit the UTF-8 byte limit"}],!1}else return A.errors=[{instancePath:i,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return A.errors=t,r===0}A.evaluated={dynamicProps:!1,dynamicItems:!1};function v(e,{instancePath:i="",parentData:d,parentDataProperty:p,rootData:u=e,dynamicAnchors:m={}}={}){let t=null,r=0,o=v.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),!(typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)))return v.errors=[{instancePath:i,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;if(r===0&&typeof e=="number"&&isFinite(e)){if(e>2147483647||isNaN(e))return v.errors=[{instancePath:i,schemaPath:"#/maximum",keyword:"maximum",params:{comparison:"<=",limit:2147483647},message:"must be <= 2147483647"}],!1;if(e<-2147483648||isNaN(e))return v.errors=[{instancePath:i,schemaPath:"#/minimum",keyword:"minimum",params:{comparison:">=",limit:-2147483648},message:"must be >= -2147483648"}],!1}return v.errors=t,r===0}v.evaluated={dynamicProps:!1,dynamicItems:!1};var M={type:"integer",enum:[0,1],$id:"urn:lightnvr:contract:StreamProtocol"};function O(e,{instancePath:i="",parentData:d,parentDataProperty:p,rootData:u=e,dynamicAnchors:m={}}={}){let t=null,r=0,o=O.evaluated;return o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)?e===0||e===1?(O.errors=t,r===0):(O.errors=[{instancePath:i,schemaPath:"#/enum",keyword:"enum",params:{allowedValues:M.enum},message:"must be equal to one of the allowed values"}],!1):(O.errors=[{instancePath:i,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1)}O.evaluated={dynamicProps:!1,dynamicItems:!1};var R={type:"string",enum:["Running","Degraded","Error","Stopped","Unknown"],$id:"urn:lightnvr:contract:SummaryStatus"};function S(e,{instancePath:i="",parentData:d,parentDataProperty:p,rootData:u=e,dynamicAnchors:m={}}={}){let t=null,r=0,o=S.evaluated;return o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e!="string"?(S.errors=[{instancePath:i,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1):e==="Running"||e==="Degraded"||e==="Error"||e==="Stopped"||e==="Unknown"?(S.errors=t,r===0):(S.errors=[{instancePath:i,schemaPath:"#/enum",keyword:"enum",params:{allowedValues:R.enum},message:"must be equal to one of the allowed values"}],!1)}S.evaluated={dynamicProps:!1,dynamicItems:!1};var B={type:"string",enum:["live","offline","never_connected","disabled"],$id:"urn:lightnvr:contract:Availability"};function $(e,{instancePath:i="",parentData:d,parentDataProperty:p,rootData:u=e,dynamicAnchors:m={}}={}){let t=null,r=0,o=$.evaluated;return o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e!="string"?($.errors=[{instancePath:i,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1):e==="live"||e==="offline"||e==="never_connected"||e==="disabled"?($.errors=t,r===0):($.errors=[{instancePath:i,schemaPath:"#/enum",keyword:"enum",params:{allowedValues:B.enum},message:"must be equal to one of the allowed values"}],!1)}$.evaluated={dynamicProps:!1,dynamicItems:!1};function b(e,{instancePath:i="",parentData:d,parentDataProperty:p,rootData:u=e,dynamicAnchors:m={}}={}){let t=null,r=0,o=b.evaluated;return o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)?r===0&&typeof e=="number"&&isFinite(e)&&(e<0||isNaN(e))?(b.errors=[{instancePath:i,schemaPath:"#/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1):(b.errors=t,r===0):(b.errors=[{instancePath:i,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1)}b.evaluated={dynamicProps:!1,dynamicItems:!1};function f(e,{instancePath:i="",parentData:d,parentDataProperty:p,rootData:u=e,dynamicAnchors:m={}}={}){let t=null,r=0,o=f.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),j(e,{instancePath:i,parentData:d,parentDataProperty:p,rootData:u,dynamicAnchors:m})||(t=t===null?j.errors:t.concat(j.errors),r=t.length),r===0)if(e&&typeof e=="object"&&!Array.isArray(e)){let l;if(e.camera_uuid===void 0&&(l="camera_uuid")||e.name===void 0&&(l="name")||e.location_uuid===void 0&&(l="location_uuid")||e.location_name===void 0&&(l="location_name")||e.location_path===void 0&&(l="location_path")||e.enabled===void 0&&(l="enabled")||e.width===void 0&&(l="width")||e.height===void 0&&(l="height")||e.fps===void 0&&(l="fps")||e.codec===void 0&&(l="codec")||e.protocol===void 0&&(l="protocol")||e.record===void 0&&(l="record")||e.record_on_schedule===void 0&&(l="record_on_schedule")||e.detection_based_recording===void 0&&(l="detection_based_recording")||e.isOnvif===void 0&&(l="isOnvif")||e.tags===void 0&&(l="tags")||e.status===void 0&&(l="status")||e.availability===void 0&&(l="availability")||e.first_video_at===void 0&&(l="first_video_at")||e.last_frame_ts===void 0&&(l="last_frame_ts")||e.current_fps===void 0&&(l="current_fps")||e.recording_active===void 0&&(l="recording_active")||e.last_recording_at===void 0&&(l="last_recording_at")||e.can_configure===void 0&&(l="can_configure"))return f.errors=[{instancePath:i,schemaPath:"#/required",keyword:"required",params:{missingProperty:l},message:"must have required property '"+l+"'"}],!1;if(e.camera_uuid!==void 0){let s=r;P(e.camera_uuid,{instancePath:i+"/camera_uuid",parentData:e,parentDataProperty:"camera_uuid",rootData:u,dynamicAnchors:m})||(t=t===null?P.errors:t.concat(P.errors),r=t.length);var n=s===r}else var n=!0;if(n){if(e.name!==void 0){let s=r;A(e.name,{instancePath:i+"/name",parentData:e,parentDataProperty:"name",rootData:u,dynamicAnchors:m})||(t=t===null?A.errors:t.concat(A.errors),r=t.length);var n=s===r}else var n=!0;if(n){if(e.location_uuid!==void 0){let s=r;P(e.location_uuid,{instancePath:i+"/location_uuid",parentData:e,parentDataProperty:"location_uuid",rootData:u,dynamicAnchors:m})||(t=t===null?P.errors:t.concat(P.errors),r=t.length);var n=s===r}else var n=!0;if(n){if(e.location_name!==void 0){let s=e.location_name,a=r;if(r===a)if(typeof s=="string"){if(F(s)>127)return f.errors=[{instancePath:i+"/location_name",schemaPath:"#/properties/location_name/maxLength",keyword:"maxLength",params:{limit:127},message:"must NOT have more than 127 characters"}],!1}else return f.errors=[{instancePath:i+"/location_name",schemaPath:"#/properties/location_name/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=a===r}else var n=!0;if(n){if(e.location_path!==void 0){let s=e.location_path,a=r;if(r===a)if(typeof s=="string"){if(F(s)>1023)return f.errors=[{instancePath:i+"/location_path",schemaPath:"#/properties/location_path/maxLength",keyword:"maxLength",params:{limit:1023},message:"must NOT have more than 1023 characters"}],!1}else return f.errors=[{instancePath:i+"/location_path",schemaPath:"#/properties/location_path/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=a===r}else var n=!0;if(n){if(e.enabled!==void 0){let s=r;if(typeof e.enabled!="boolean")return f.errors=[{instancePath:i+"/enabled",schemaPath:"#/properties/enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=s===r}else var n=!0;if(n){if(e.width!==void 0){let s=r;v(e.width,{instancePath:i+"/width",parentData:e,parentDataProperty:"width",rootData:u,dynamicAnchors:m})||(t=t===null?v.errors:t.concat(v.errors),r=t.length);var n=s===r}else var n=!0;if(n){if(e.height!==void 0){let s=r;v(e.height,{instancePath:i+"/height",parentData:e,parentDataProperty:"height",rootData:u,dynamicAnchors:m})||(t=t===null?v.errors:t.concat(v.errors),r=t.length);var n=s===r}else var n=!0;if(n){if(e.fps!==void 0){let s=r;v(e.fps,{instancePath:i+"/fps",parentData:e,parentDataProperty:"fps",rootData:u,dynamicAnchors:m})||(t=t===null?v.errors:t.concat(v.errors),r=t.length);var n=s===r}else var n=!0;if(n){if(e.codec!==void 0){let s=e.codec,a=r;if(r===a)if(typeof s=="string"){if(F(s)>15)return f.errors=[{instancePath:i+"/codec",schemaPath:"#/properties/codec/maxLength",keyword:"maxLength",params:{limit:15},message:"must NOT have more than 15 characters"}],!1}else return f.errors=[{instancePath:i+"/codec",schemaPath:"#/properties/codec/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=a===r}else var n=!0;if(n){if(e.protocol!==void 0){let s=r;O(e.protocol,{instancePath:i+"/protocol",parentData:e,parentDataProperty:"protocol",rootData:u,dynamicAnchors:m})||(t=t===null?O.errors:t.concat(O.errors),r=t.length);var n=s===r}else var n=!0;if(n){if(e.record!==void 0){let s=r;if(typeof e.record!="boolean")return f.errors=[{instancePath:i+"/record",schemaPath:"#/properties/record/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=s===r}else var n=!0;if(n){if(e.record_on_schedule!==void 0){let s=r;if(typeof e.record_on_schedule!="boolean")return f.errors=[{instancePath:i+"/record_on_schedule",schemaPath:"#/properties/record_on_schedule/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=s===r}else var n=!0;if(n){if(e.detection_based_recording!==void 0){let s=r;if(typeof e.detection_based_recording!="boolean")return f.errors=[{instancePath:i+"/detection_based_recording",schemaPath:"#/properties/detection_based_recording/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=s===r}else var n=!0;if(n){if(e.isOnvif!==void 0){let s=r;if(typeof e.isOnvif!="boolean")return f.errors=[{instancePath:i+"/isOnvif",schemaPath:"#/properties/isOnvif/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=s===r}else var n=!0;if(n){if(e.tags!==void 0){let s=e.tags,a=r;if(r===a)if(typeof s=="string"){if(F(s)>255)return f.errors=[{instancePath:i+"/tags",schemaPath:"#/properties/tags/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1}else return f.errors=[{instancePath:i+"/tags",schemaPath:"#/properties/tags/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=a===r}else var n=!0;if(n){if(e.status!==void 0){let s=r;S(e.status,{instancePath:i+"/status",parentData:e,parentDataProperty:"status",rootData:u,dynamicAnchors:m})||(t=t===null?S.errors:t.concat(S.errors),r=t.length);var n=s===r}else var n=!0;if(n){if(e.availability!==void 0){let s=r;$(e.availability,{instancePath:i+"/availability",parentData:e,parentDataProperty:"availability",rootData:u,dynamicAnchors:m})||(t=t===null?$.errors:t.concat($.errors),r=t.length);var n=s===r}else var n=!0;if(n){if(e.first_video_at!==void 0){let s=r;b(e.first_video_at,{instancePath:i+"/first_video_at",parentData:e,parentDataProperty:"first_video_at",rootData:u,dynamicAnchors:m})||(t=t===null?b.errors:t.concat(b.errors),r=t.length);var n=s===r}else var n=!0;if(n){if(e.last_frame_ts!==void 0){let s=r;b(e.last_frame_ts,{instancePath:i+"/last_frame_ts",parentData:e,parentDataProperty:"last_frame_ts",rootData:u,dynamicAnchors:m})||(t=t===null?b.errors:t.concat(b.errors),r=t.length);var n=s===r}else var n=!0;if(n){if(e.current_fps!==void 0){let s=e.current_fps,a=r;if(r===a)if(typeof s=="number"&&isFinite(s)){if(s<0||isNaN(s))return f.errors=[{instancePath:i+"/current_fps",schemaPath:"#/properties/current_fps/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1}else return f.errors=[{instancePath:i+"/current_fps",schemaPath:"#/properties/current_fps/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var n=a===r}else var n=!0;if(n){if(e.recording_active!==void 0){let s=r;if(typeof e.recording_active!="boolean")return f.errors=[{instancePath:i+"/recording_active",schemaPath:"#/properties/recording_active/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=s===r}else var n=!0;if(n){if(e.last_recording_at!==void 0){let s=r;b(e.last_recording_at,{instancePath:i+"/last_recording_at",parentData:e,parentDataProperty:"last_recording_at",rootData:u,dynamicAnchors:m})||(t=t===null?b.errors:t.concat(b.errors),r=t.length);var n=s===r}else var n=!0;if(n)if(e.can_configure!==void 0){let s=r;if(typeof e.can_configure!="boolean")return f.errors=[{instancePath:i+"/can_configure",schemaPath:"#/properties/can_configure/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=s===r}else var n=!0}}}}}}}}}}}}}}}}}}}}}}}else return f.errors=[{instancePath:i,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return f.errors=t,r===0}f.evaluated={props:{camera_uuid:!0,name:!0,location_uuid:!0,location_name:!0,location_path:!0,enabled:!0,width:!0,height:!0,fps:!0,codec:!0,protocol:!0,record:!0,record_on_schedule:!0,detection_based_recording:!0,isOnvif:!0,tags:!0,status:!0,availability:!0,first_video_at:!0,last_frame_ts:!0,current_fps:!0,recording_active:!0,last_recording_at:!0,can_configure:!0},dynamicProps:!1,dynamicItems:!1};function I(e,{instancePath:i="",parentData:d,parentDataProperty:p,rootData:u=e,dynamicAnchors:m={}}={}){let t=null,r=0,o=I.evaluated;return o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e!="string"?(I.errors=[{instancePath:i,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1):e!==""?(I.errors=[{instancePath:i,schemaPath:"#/const",keyword:"const",params:{allowedValue:""},message:"must be equal to constant"}],!1):(I.errors=t,r===0)}I.evaluated={dynamicProps:!1,dynamicItems:!1};function x(e,{instancePath:i="",parentData:d,parentDataProperty:p,rootData:u=e,dynamicAnchors:m={}}={}){let t=null,r=0,o=x.evaluated;o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0);let n=r;f(e,{instancePath:i,parentData:d,parentDataProperty:p,rootData:u,dynamicAnchors:m})||(t=t===null?f.errors:t.concat(f.errors),r=t.length);var l=n===r;if(l){let q=r,D=r,w=!0,L=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let _;if(e.can_configure===void 0&&(_="can_configure")){let k={};t===null?t=[k]:t.push(k),r++}else if(e.can_configure!==void 0&&e.can_configure!==!1){let k={};t===null?t=[k]:t.push(k),r++}}var s=L===r;if(r=D,t!==null&&(D?t.length=D:t=null),s){let _=r;e&&typeof e=="object"&&!Array.isArray(e)&&e.admin_url!==void 0&&(I(e.admin_url,{instancePath:i+"/admin_url",parentData:e,parentDataProperty:"admin_url",rootData:u,dynamicAnchors:m})||(t=t===null?I.errors:t.concat(I.errors),r=t.length));var s=_===r;if(w=s,w){var a={};a.admin_url=!0,a.can_configure=!0}}if(!w){let _={instancePath:i,schemaPath:"#/allOf/1/if",keyword:"if",params:{failingKeyword:"then"},message:'must match "then" schema'};return t===null?t=[_]:t.push(_),r++,x.errors=t,!1}if(r===q)if(e&&typeof e=="object"&&!Array.isArray(e)){if(a!==!0&&(a=a||{},a.admin_url=!0),e.admin_url!==void 0){let _=e.admin_url;if(r===r)if(typeof _=="string"){if(F(_)>511)return x.errors=[{instancePath:i+"/admin_url",schemaPath:"#/allOf/1/properties/admin_url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return x.errors=[{instancePath:i+"/admin_url",schemaPath:"#/allOf/1/properties/admin_url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1}}else return x.errors=[{instancePath:i,schemaPath:"#/allOf/1/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;var l=q===r;l&&a!==!0&&(a=a||{},a.camera_uuid=!0,a.name=!0,a.location_uuid=!0,a.location_name=!0,a.location_path=!0,a.enabled=!0,a.width=!0,a.height=!0,a.fps=!0,a.codec=!0,a.protocol=!0,a.record=!0,a.record_on_schedule=!0,a.detection_based_recording=!0,a.isOnvif=!0,a.tags=!0,a.status=!0,a.availability=!0,a.first_video_at=!0,a.last_frame_ts=!0,a.current_fps=!0,a.recording_active=!0,a.last_recording_at=!0,a.can_configure=!0)}return x.errors=t,o.props=a,r===0}x.evaluated={dynamicProps:!0,dynamicItems:!1};function N(e,{instancePath:i="",parentData:d,parentDataProperty:p,rootData:u=e,dynamicAnchors:m={}}={}){let t=null,r=0,o=N.evaluated;o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0);let n=r;g(e,{instancePath:i,parentData:d,parentDataProperty:p,rootData:u,dynamicAnchors:m})||(t=t===null?g.errors:t.concat(g.errors),r=t.length);var l=n===r;if(l){let a=r;if(r===a)if(e&&typeof e=="object"&&!Array.isArray(e)){let D;if(e.streams===void 0&&(D="streams"))return N.errors=[{instancePath:i,schemaPath:"#/allOf/1/required",keyword:"required",params:{missingProperty:D},message:"must have required property '"+D+"'"}],!1;if(e.streams!==void 0){let w=e.streams;if(r===r)if(Array.isArray(w)){if(w.length>100)return N.errors=[{instancePath:i+"/streams",schemaPath:"#/allOf/1/properties/streams/maxItems",keyword:"maxItems",params:{limit:100},message:"must NOT have more than 100 items"}],!1;{var s=!0;let E=w.length;for(let _=0;_()=>{try{return i||e((i={exports:{}}).exports,i),i.exports}catch(_){throw i=0,_}};var X=M(E=>{"use strict";Object.defineProperty(E,"__esModule",{value:!0});function V(e){let i=e.length,_=0,d=0,m;for(;d=55296&&m<=56319&&d=",limit:0},message:"must be >= 0"}],!1;var a=n===r}else var a=!0;if(a){if(e.page!==void 0){let t=e.page,n=r;if(!(typeof t=="number"&&!(t%1)&&!isNaN(t)&&isFinite(t)))return g.errors=[{instancePath:i+"/page",schemaPath:"#/properties/page/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;if(r===n&&typeof t=="number"&&isFinite(t)){if(t>1e6||isNaN(t))return g.errors=[{instancePath:i+"/page",schemaPath:"#/properties/page/maximum",keyword:"maximum",params:{comparison:"<=",limit:1e6},message:"must be <= 1000000"}],!1;if(t<1||isNaN(t))return g.errors=[{instancePath:i+"/page",schemaPath:"#/properties/page/minimum",keyword:"minimum",params:{comparison:">=",limit:1},message:"must be >= 1"}],!1}var a=n===r}else var a=!0;if(a){if(e.page_size!==void 0){let t=e.page_size,n=r;if(!(typeof t=="number"&&!(t%1)&&!isNaN(t)&&isFinite(t)))return g.errors=[{instancePath:i+"/page_size",schemaPath:"#/properties/page_size/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;if(r===n&&typeof t=="number"&&isFinite(t)){if(t>100||isNaN(t))return g.errors=[{instancePath:i+"/page_size",schemaPath:"#/properties/page_size/maximum",keyword:"maximum",params:{comparison:"<=",limit:100},message:"must be <= 100"}],!1;if(t<1||isNaN(t))return g.errors=[{instancePath:i+"/page_size",schemaPath:"#/properties/page_size/minimum",keyword:"minimum",params:{comparison:">=",limit:1},message:"must be >= 1"}],!1}var a=n===r}else var a=!0;if(a)if(e.total_pages!==void 0){let t=e.total_pages,n=r;if(!(typeof t=="number"&&!(t%1)&&!isNaN(t)&&isFinite(t)))return g.errors=[{instancePath:i+"/total_pages",schemaPath:"#/properties/total_pages/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;if(r===n&&typeof t=="number"&&isFinite(t)&&(t<0||isNaN(t)))return g.errors=[{instancePath:i+"/total_pages",schemaPath:"#/properties/total_pages/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1;var a=n===r}else var a=!0}}}else return g.errors=[{instancePath:i,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return g.errors=s,r===0}g.evaluated={props:{total:!0,page:!0,page_size:!0,total_pages:!0},dynamicProps:!1,dynamicItems:!1};function z(e,{instancePath:i="",parentData:_,parentDataProperty:d,rootData:m=e,dynamicAnchors:f={}}={}){let s=null,r=0,o=z.evaluated;o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0);let a=r,l=r,t=r,n=!1,T=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let h;if(e.url===void 0&&(h="url")){let y={};s===null?s=[y]:s.push(y),r++}}var w=T===r;n=n||w;let D=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let h;if(e.onvif_username===void 0&&(h="onvif_username")){let y={};s===null?s=[y]:s.push(y),r++}}var w=D===r;n=n||w;let A=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let h;if(e.onvif_password===void 0&&(h="onvif_password")){let y={};s===null?s=[y]:s.push(y),r++}}var w=A===r;n=n||w;let Y=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let h;if(e.sub_stream_url===void 0&&(h="sub_stream_url")){let y={};s===null?s=[y]:s.push(y),r++}}var w=Y===r;n=n||w;let v=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let h;if(e.detection_url===void 0&&(h="detection_url")){let y={};s===null?s=[y]:s.push(y),r++}}var w=v===r;n=n||w;let p=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let h;if(e.publish_url===void 0&&(h="publish_url")){let y={};s===null?s=[y]:s.push(y),r++}}var w=p===r;n=n||w;let L=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let h;if(e.go2rtc_source_override===void 0&&(h="go2rtc_source_override")){let y={};s===null?s=[y]:s.push(y),r++}}var w=L===r;n=n||w;let C=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let h;if(e.detection_api_url===void 0&&(h="detection_api_url")){let y={};s===null?s=[y]:s.push(y),r++}}var w=C===r;if(n=n||w,n)r=t,s!==null&&(t?s.length=t:s=null);else{let h={};s===null?s=[h]:s.push(h),r++}var U=l===r;return U?(z.errors=[{instancePath:i,schemaPath:"#/not",keyword:"not",params:{},message:"must NOT be valid"}],!1):(r=a,s!==null&&(a?s.length=a:s=null),z.errors=s,r===0)}z.evaluated={dynamicProps:!1,dynamicItems:!1};var O=X().default,Z=new RegExp("^(|[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})$","u");function N(e,{instancePath:i="",parentData:_,parentDataProperty:d,rootData:m=e,dynamicAnchors:f={}}={}){let s=null,r=0,o=N.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(typeof e=="string"){if(O(e)>36)return N.errors=[{instancePath:i,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:36},message:"must NOT have more than 36 characters"}],!1;if(!Z.test(e))return N.errors=[{instancePath:i,schemaPath:"#/pattern",keyword:"pattern",params:{pattern:"^(|[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})$"},message:'must match pattern "^(|[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})$"'}],!1}else return N.errors=[{instancePath:i,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return N.errors=s,r===0}N.evaluated={dynamicProps:!1,dynamicItems:!1};function x(e,{instancePath:i="",parentData:_,parentDataProperty:d,rootData:m=e,dynamicAnchors:f={}}={}){let s=null,r=0,o=x.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(typeof e=="string"){if(O(e)>255)return x.errors=[{instancePath:i,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1;if(O(e)<1)return x.errors=[{instancePath:i,schemaPath:"#/minLength",keyword:"minLength",params:{limit:1},message:"must NOT have fewer than 1 characters"}],!1;if(new TextEncoder().encode(e).byteLength>255)return x.errors=[{instancePath:i,schemaPath:"#/x-lightnvr-max-bytes",keyword:"x-lightnvr-max-bytes",params:{},message:"must fit the UTF-8 byte limit"}],!1}else return x.errors=[{instancePath:i,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return x.errors=s,r===0}x.evaluated={dynamicProps:!1,dynamicItems:!1};function P(e,{instancePath:i="",parentData:_,parentDataProperty:d,rootData:m=e,dynamicAnchors:f={}}={}){let s=null,r=0,o=P.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),!(typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)))return P.errors=[{instancePath:i,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;if(r===0&&typeof e=="number"&&isFinite(e)){if(e>2147483647||isNaN(e))return P.errors=[{instancePath:i,schemaPath:"#/maximum",keyword:"maximum",params:{comparison:"<=",limit:2147483647},message:"must be <= 2147483647"}],!1;if(e<-2147483648||isNaN(e))return P.errors=[{instancePath:i,schemaPath:"#/minimum",keyword:"minimum",params:{comparison:">=",limit:-2147483648},message:"must be >= -2147483648"}],!1}return P.errors=s,r===0}P.evaluated={dynamicProps:!1,dynamicItems:!1};var R={type:"integer",enum:[0,1],$id:"urn:lightnvr:contract:StreamProtocol"};function q(e,{instancePath:i="",parentData:_,parentDataProperty:d,rootData:m=e,dynamicAnchors:f={}}={}){let s=null,r=0,o=q.evaluated;return o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)?e===0||e===1?(q.errors=s,r===0):(q.errors=[{instancePath:i,schemaPath:"#/enum",keyword:"enum",params:{allowedValues:R.enum},message:"must be equal to one of the allowed values"}],!1):(q.errors=[{instancePath:i,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1)}q.evaluated={dynamicProps:!1,dynamicItems:!1};var B={type:"string",enum:["Running","Degraded","Error","Stopped","Unknown"],$id:"urn:lightnvr:contract:SummaryStatus"};function S(e,{instancePath:i="",parentData:_,parentDataProperty:d,rootData:m=e,dynamicAnchors:f={}}={}){let s=null,r=0,o=S.evaluated;return o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e!="string"?(S.errors=[{instancePath:i,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1):e==="Running"||e==="Degraded"||e==="Error"||e==="Stopped"||e==="Unknown"?(S.errors=s,r===0):(S.errors=[{instancePath:i,schemaPath:"#/enum",keyword:"enum",params:{allowedValues:B.enum},message:"must be equal to one of the allowed values"}],!1)}S.evaluated={dynamicProps:!1,dynamicItems:!1};var J={type:"string",enum:["live","offline","never_connected","disabled"],$id:"urn:lightnvr:contract:Availability"};function $(e,{instancePath:i="",parentData:_,parentDataProperty:d,rootData:m=e,dynamicAnchors:f={}}={}){let s=null,r=0,o=$.evaluated;return o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e!="string"?($.errors=[{instancePath:i,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1):e==="live"||e==="offline"||e==="never_connected"||e==="disabled"?($.errors=s,r===0):($.errors=[{instancePath:i,schemaPath:"#/enum",keyword:"enum",params:{allowedValues:J.enum},message:"must be equal to one of the allowed values"}],!1)}$.evaluated={dynamicProps:!1,dynamicItems:!1};function k(e,{instancePath:i="",parentData:_,parentDataProperty:d,rootData:m=e,dynamicAnchors:f={}}={}){let s=null,r=0,o=k.evaluated;return o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)?r===0&&typeof e=="number"&&isFinite(e)&&(e<0||isNaN(e))?(k.errors=[{instancePath:i,schemaPath:"#/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1):(k.errors=s,r===0):(k.errors=[{instancePath:i,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1)}k.evaluated={dynamicProps:!1,dynamicItems:!1};function c(e,{instancePath:i="",parentData:_,parentDataProperty:d,rootData:m=e,dynamicAnchors:f={}}={}){let s=null,r=0,o=c.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),z(e,{instancePath:i,parentData:_,parentDataProperty:d,rootData:m,dynamicAnchors:f})||(s=s===null?z.errors:s.concat(z.errors),r=s.length),r===0)if(e&&typeof e=="object"&&!Array.isArray(e)){let l;if(e.camera_uuid===void 0&&(l="camera_uuid")||e.name===void 0&&(l="name")||e.location_uuid===void 0&&(l="location_uuid")||e.location_name===void 0&&(l="location_name")||e.location_path===void 0&&(l="location_path")||e.enabled===void 0&&(l="enabled")||e.width===void 0&&(l="width")||e.height===void 0&&(l="height")||e.fps===void 0&&(l="fps")||e.codec===void 0&&(l="codec")||e.protocol===void 0&&(l="protocol")||e.record===void 0&&(l="record")||e.record_on_schedule===void 0&&(l="record_on_schedule")||e.detection_based_recording===void 0&&(l="detection_based_recording")||e.isOnvif===void 0&&(l="isOnvif")||e.tags===void 0&&(l="tags")||e.status===void 0&&(l="status")||e.availability===void 0&&(l="availability")||e.first_video_at===void 0&&(l="first_video_at")||e.last_frame_ts===void 0&&(l="last_frame_ts")||e.current_fps===void 0&&(l="current_fps")||e.recording_active===void 0&&(l="recording_active")||e.last_recording_at===void 0&&(l="last_recording_at")||e.can_configure===void 0&&(l="can_configure"))return c.errors=[{instancePath:i,schemaPath:"#/required",keyword:"required",params:{missingProperty:l},message:"must have required property '"+l+"'"}],!1;if(e.camera_uuid!==void 0){let t=r;N(e.camera_uuid,{instancePath:i+"/camera_uuid",parentData:e,parentDataProperty:"camera_uuid",rootData:m,dynamicAnchors:f})||(s=s===null?N.errors:s.concat(N.errors),r=s.length);var a=t===r}else var a=!0;if(a){if(e.name!==void 0){let t=r;x(e.name,{instancePath:i+"/name",parentData:e,parentDataProperty:"name",rootData:m,dynamicAnchors:f})||(s=s===null?x.errors:s.concat(x.errors),r=s.length);var a=t===r}else var a=!0;if(a){if(e.location_uuid!==void 0){let t=r;N(e.location_uuid,{instancePath:i+"/location_uuid",parentData:e,parentDataProperty:"location_uuid",rootData:m,dynamicAnchors:f})||(s=s===null?N.errors:s.concat(N.errors),r=s.length);var a=t===r}else var a=!0;if(a){if(e.location_name!==void 0){let t=e.location_name,n=r;if(r===n)if(typeof t=="string"){if(O(t)>127)return c.errors=[{instancePath:i+"/location_name",schemaPath:"#/properties/location_name/maxLength",keyword:"maxLength",params:{limit:127},message:"must NOT have more than 127 characters"}],!1}else return c.errors=[{instancePath:i+"/location_name",schemaPath:"#/properties/location_name/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var a=n===r}else var a=!0;if(a){if(e.location_path!==void 0){let t=e.location_path,n=r;if(r===n)if(typeof t=="string"){if(O(t)>1023)return c.errors=[{instancePath:i+"/location_path",schemaPath:"#/properties/location_path/maxLength",keyword:"maxLength",params:{limit:1023},message:"must NOT have more than 1023 characters"}],!1}else return c.errors=[{instancePath:i+"/location_path",schemaPath:"#/properties/location_path/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var a=n===r}else var a=!0;if(a){if(e.enabled!==void 0){let t=r;if(typeof e.enabled!="boolean")return c.errors=[{instancePath:i+"/enabled",schemaPath:"#/properties/enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var a=t===r}else var a=!0;if(a){if(e.width!==void 0){let t=r;P(e.width,{instancePath:i+"/width",parentData:e,parentDataProperty:"width",rootData:m,dynamicAnchors:f})||(s=s===null?P.errors:s.concat(P.errors),r=s.length);var a=t===r}else var a=!0;if(a){if(e.height!==void 0){let t=r;P(e.height,{instancePath:i+"/height",parentData:e,parentDataProperty:"height",rootData:m,dynamicAnchors:f})||(s=s===null?P.errors:s.concat(P.errors),r=s.length);var a=t===r}else var a=!0;if(a){if(e.fps!==void 0){let t=r;P(e.fps,{instancePath:i+"/fps",parentData:e,parentDataProperty:"fps",rootData:m,dynamicAnchors:f})||(s=s===null?P.errors:s.concat(P.errors),r=s.length);var a=t===r}else var a=!0;if(a){if(e.codec!==void 0){let t=e.codec,n=r;if(r===n)if(typeof t=="string"){if(O(t)>15)return c.errors=[{instancePath:i+"/codec",schemaPath:"#/properties/codec/maxLength",keyword:"maxLength",params:{limit:15},message:"must NOT have more than 15 characters"}],!1}else return c.errors=[{instancePath:i+"/codec",schemaPath:"#/properties/codec/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var a=n===r}else var a=!0;if(a){if(e.protocol!==void 0){let t=r;q(e.protocol,{instancePath:i+"/protocol",parentData:e,parentDataProperty:"protocol",rootData:m,dynamicAnchors:f})||(s=s===null?q.errors:s.concat(q.errors),r=s.length);var a=t===r}else var a=!0;if(a){if(e.record!==void 0){let t=r;if(typeof e.record!="boolean")return c.errors=[{instancePath:i+"/record",schemaPath:"#/properties/record/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var a=t===r}else var a=!0;if(a){if(e.record_on_schedule!==void 0){let t=r;if(typeof e.record_on_schedule!="boolean")return c.errors=[{instancePath:i+"/record_on_schedule",schemaPath:"#/properties/record_on_schedule/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var a=t===r}else var a=!0;if(a){if(e.detection_based_recording!==void 0){let t=r;if(typeof e.detection_based_recording!="boolean")return c.errors=[{instancePath:i+"/detection_based_recording",schemaPath:"#/properties/detection_based_recording/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var a=t===r}else var a=!0;if(a){if(e.isOnvif!==void 0){let t=r;if(typeof e.isOnvif!="boolean")return c.errors=[{instancePath:i+"/isOnvif",schemaPath:"#/properties/isOnvif/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var a=t===r}else var a=!0;if(a){if(e.tags!==void 0){let t=e.tags,n=r;if(r===n)if(typeof t=="string"){if(O(t)>255)return c.errors=[{instancePath:i+"/tags",schemaPath:"#/properties/tags/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1}else return c.errors=[{instancePath:i+"/tags",schemaPath:"#/properties/tags/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var a=n===r}else var a=!0;if(a){if(e.status!==void 0){let t=r;S(e.status,{instancePath:i+"/status",parentData:e,parentDataProperty:"status",rootData:m,dynamicAnchors:f})||(s=s===null?S.errors:s.concat(S.errors),r=s.length);var a=t===r}else var a=!0;if(a){if(e.availability!==void 0){let t=r;$(e.availability,{instancePath:i+"/availability",parentData:e,parentDataProperty:"availability",rootData:m,dynamicAnchors:f})||(s=s===null?$.errors:s.concat($.errors),r=s.length);var a=t===r}else var a=!0;if(a){if(e.first_video_at!==void 0){let t=r;k(e.first_video_at,{instancePath:i+"/first_video_at",parentData:e,parentDataProperty:"first_video_at",rootData:m,dynamicAnchors:f})||(s=s===null?k.errors:s.concat(k.errors),r=s.length);var a=t===r}else var a=!0;if(a){if(e.last_frame_ts!==void 0){let t=r;k(e.last_frame_ts,{instancePath:i+"/last_frame_ts",parentData:e,parentDataProperty:"last_frame_ts",rootData:m,dynamicAnchors:f})||(s=s===null?k.errors:s.concat(k.errors),r=s.length);var a=t===r}else var a=!0;if(a){if(e.current_fps!==void 0){let t=e.current_fps,n=r;if(r===n)if(typeof t=="number"&&isFinite(t)){if(t<0||isNaN(t))return c.errors=[{instancePath:i+"/current_fps",schemaPath:"#/properties/current_fps/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1}else return c.errors=[{instancePath:i+"/current_fps",schemaPath:"#/properties/current_fps/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var a=n===r}else var a=!0;if(a){if(e.recording_active!==void 0){let t=r;if(typeof e.recording_active!="boolean")return c.errors=[{instancePath:i+"/recording_active",schemaPath:"#/properties/recording_active/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var a=t===r}else var a=!0;if(a){if(e.last_recording_at!==void 0){let t=r;k(e.last_recording_at,{instancePath:i+"/last_recording_at",parentData:e,parentDataProperty:"last_recording_at",rootData:m,dynamicAnchors:f})||(s=s===null?k.errors:s.concat(k.errors),r=s.length);var a=t===r}else var a=!0;if(a)if(e.can_configure!==void 0){let t=r;if(typeof e.can_configure!="boolean")return c.errors=[{instancePath:i+"/can_configure",schemaPath:"#/properties/can_configure/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var a=t===r}else var a=!0}}}}}}}}}}}}}}}}}}}}}}}else return c.errors=[{instancePath:i,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return c.errors=s,r===0}c.evaluated={props:{camera_uuid:!0,name:!0,location_uuid:!0,location_name:!0,location_path:!0,enabled:!0,width:!0,height:!0,fps:!0,codec:!0,protocol:!0,record:!0,record_on_schedule:!0,detection_based_recording:!0,isOnvif:!0,tags:!0,status:!0,availability:!0,first_video_at:!0,last_frame_ts:!0,current_fps:!0,recording_active:!0,last_recording_at:!0,can_configure:!0},dynamicProps:!1,dynamicItems:!1};var K={type:"string",enum:["auto","webrtc_only","mse_only","hls_only","webrtc_then_mse","mse_then_hls"],$id:"urn:lightnvr:contract:PlaybackTransport"};function I(e,{instancePath:i="",parentData:_,parentDataProperty:d,rootData:m=e,dynamicAnchors:f={}}={}){let s=null,r=0,o=I.evaluated;return o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e!="string"?(I.errors=[{instancePath:i,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1):e==="auto"||e==="webrtc_only"||e==="mse_only"||e==="hls_only"||e==="webrtc_then_mse"||e==="mse_then_hls"?(I.errors=s,r===0):(I.errors=[{instancePath:i,schemaPath:"#/enum",keyword:"enum",params:{allowedValues:K.enum},message:"must be equal to one of the allowed values"}],!1)}I.evaluated={dynamicProps:!1,dynamicItems:!1};function u(e,{instancePath:i="",parentData:_,parentDataProperty:d,rootData:m=e,dynamicAnchors:f={}}={}){let s=null,r=0,o=u.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(e&&typeof e=="object"&&!Array.isArray(e)){let l;if(e.version===void 0&&(l="version")||e.projection===void 0&&(l="projection")||e.mount===void 0&&(l="mount")||e.centerX===void 0&&(l="centerX")||e.centerY===void 0&&(l="centerY")||e.radius===void 0&&(l="radius")||e.fov===void 0&&(l="fov")||e.rotation===void 0&&(l="rotation")||e.defaultYaw===void 0&&(l="defaultYaw")||e.defaultTilt===void 0&&(l="defaultTilt")||e.defaultViewFov===void 0&&(l="defaultViewFov"))return u.errors=[{instancePath:i,schemaPath:"#/required",keyword:"required",params:{missingProperty:l},message:"must have required property '"+l+"'"}],!1;if(e.version!==void 0){let t=r;if(e.version!==1)return u.errors=[{instancePath:i+"/version",schemaPath:"#/properties/version/const",keyword:"const",params:{allowedValue:1},message:"must be equal to constant"}],!1;var a=t===r}else var a=!0;if(a){if(e.projection!==void 0){let t=r;if(e.projection!=="equidistant")return u.errors=[{instancePath:i+"/projection",schemaPath:"#/properties/projection/const",keyword:"const",params:{allowedValue:"equidistant"},message:"must be equal to constant"}],!1;var a=t===r}else var a=!0;if(a){if(e.mount!==void 0){let t=r;if(e.mount!=="ceiling")return u.errors=[{instancePath:i+"/mount",schemaPath:"#/properties/mount/const",keyword:"const",params:{allowedValue:"ceiling"},message:"must be equal to constant"}],!1;var a=t===r}else var a=!0;if(a){if(e.centerX!==void 0){let t=e.centerX,n=r;if(r===n)if(typeof t=="number"&&isFinite(t)){if(t>1||isNaN(t))return u.errors=[{instancePath:i+"/centerX",schemaPath:"#/properties/centerX/maximum",keyword:"maximum",params:{comparison:"<=",limit:1},message:"must be <= 1"}],!1;if(t<0||isNaN(t))return u.errors=[{instancePath:i+"/centerX",schemaPath:"#/properties/centerX/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1}else return u.errors=[{instancePath:i+"/centerX",schemaPath:"#/properties/centerX/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var a=n===r}else var a=!0;if(a){if(e.centerY!==void 0){let t=e.centerY,n=r;if(r===n)if(typeof t=="number"&&isFinite(t)){if(t>1||isNaN(t))return u.errors=[{instancePath:i+"/centerY",schemaPath:"#/properties/centerY/maximum",keyword:"maximum",params:{comparison:"<=",limit:1},message:"must be <= 1"}],!1;if(t<0||isNaN(t))return u.errors=[{instancePath:i+"/centerY",schemaPath:"#/properties/centerY/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1}else return u.errors=[{instancePath:i+"/centerY",schemaPath:"#/properties/centerY/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var a=n===r}else var a=!0;if(a){if(e.radius!==void 0){let t=e.radius,n=r;if(r===n)if(typeof t=="number"&&isFinite(t)){if(t>1||isNaN(t))return u.errors=[{instancePath:i+"/radius",schemaPath:"#/properties/radius/maximum",keyword:"maximum",params:{comparison:"<=",limit:1},message:"must be <= 1"}],!1;if(t<.05||isNaN(t))return u.errors=[{instancePath:i+"/radius",schemaPath:"#/properties/radius/minimum",keyword:"minimum",params:{comparison:">=",limit:.05},message:"must be >= 0.05"}],!1}else return u.errors=[{instancePath:i+"/radius",schemaPath:"#/properties/radius/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var a=n===r}else var a=!0;if(a){if(e.fov!==void 0){let t=e.fov,n=r;if(r===n)if(typeof t=="number"&&isFinite(t)){if(t>360||isNaN(t))return u.errors=[{instancePath:i+"/fov",schemaPath:"#/properties/fov/maximum",keyword:"maximum",params:{comparison:"<=",limit:360},message:"must be <= 360"}],!1;if(t<160||isNaN(t))return u.errors=[{instancePath:i+"/fov",schemaPath:"#/properties/fov/minimum",keyword:"minimum",params:{comparison:">=",limit:160},message:"must be >= 160"}],!1}else return u.errors=[{instancePath:i+"/fov",schemaPath:"#/properties/fov/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var a=n===r}else var a=!0;if(a){if(e.rotation!==void 0){let t=e.rotation,n=r;if(r===n)if(typeof t=="number"&&isFinite(t)){if(t>360||isNaN(t))return u.errors=[{instancePath:i+"/rotation",schemaPath:"#/properties/rotation/maximum",keyword:"maximum",params:{comparison:"<=",limit:360},message:"must be <= 360"}],!1;if(t<-360||isNaN(t))return u.errors=[{instancePath:i+"/rotation",schemaPath:"#/properties/rotation/minimum",keyword:"minimum",params:{comparison:">=",limit:-360},message:"must be >= -360"}],!1}else return u.errors=[{instancePath:i+"/rotation",schemaPath:"#/properties/rotation/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var a=n===r}else var a=!0;if(a){if(e.defaultYaw!==void 0){let t=e.defaultYaw,n=r;if(r===n)if(typeof t=="number"&&isFinite(t)){if(t>360||isNaN(t))return u.errors=[{instancePath:i+"/defaultYaw",schemaPath:"#/properties/defaultYaw/maximum",keyword:"maximum",params:{comparison:"<=",limit:360},message:"must be <= 360"}],!1;if(t<-360||isNaN(t))return u.errors=[{instancePath:i+"/defaultYaw",schemaPath:"#/properties/defaultYaw/minimum",keyword:"minimum",params:{comparison:">=",limit:-360},message:"must be >= -360"}],!1}else return u.errors=[{instancePath:i+"/defaultYaw",schemaPath:"#/properties/defaultYaw/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var a=n===r}else var a=!0;if(a){if(e.defaultTilt!==void 0){let t=e.defaultTilt,n=r;if(r===n)if(typeof t=="number"&&isFinite(t)){if(t>30||isNaN(t))return u.errors=[{instancePath:i+"/defaultTilt",schemaPath:"#/properties/defaultTilt/maximum",keyword:"maximum",params:{comparison:"<=",limit:30},message:"must be <= 30"}],!1;if(t<-90||isNaN(t))return u.errors=[{instancePath:i+"/defaultTilt",schemaPath:"#/properties/defaultTilt/minimum",keyword:"minimum",params:{comparison:">=",limit:-90},message:"must be >= -90"}],!1}else return u.errors=[{instancePath:i+"/defaultTilt",schemaPath:"#/properties/defaultTilt/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var a=n===r}else var a=!0;if(a)if(e.defaultViewFov!==void 0){let t=e.defaultViewFov,n=r;if(r===n)if(typeof t=="number"&&isFinite(t)){if(t>120||isNaN(t))return u.errors=[{instancePath:i+"/defaultViewFov",schemaPath:"#/properties/defaultViewFov/maximum",keyword:"maximum",params:{comparison:"<=",limit:120},message:"must be <= 120"}],!1;if(t<20||isNaN(t))return u.errors=[{instancePath:i+"/defaultViewFov",schemaPath:"#/properties/defaultViewFov/minimum",keyword:"minimum",params:{comparison:">=",limit:20},message:"must be >= 20"}],!1}else return u.errors=[{instancePath:i+"/defaultViewFov",schemaPath:"#/properties/defaultViewFov/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var a=n===r}else var a=!0}}}}}}}}}}else return u.errors=[{instancePath:i,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return u.errors=s,r===0}u.evaluated={props:{version:!0,projection:!0,mount:!0,centerX:!0,centerY:!0,radius:!0,fov:!0,rotation:!0,defaultYaw:!0,defaultTilt:!0,defaultViewFov:!0},dynamicProps:!1,dynamicItems:!1};function F(e,{instancePath:i="",parentData:_,parentDataProperty:d,rootData:m=e,dynamicAnchors:f={}}={}){let s=null,r=0,o=F.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(typeof e=="string"){if(O(e)>1023)return F.errors=[{instancePath:i,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:1023},message:"must NOT have more than 1023 characters"}],!1;if(new TextEncoder().encode(e).byteLength>1023)return F.errors=[{instancePath:i,schemaPath:"#/x-lightnvr-max-bytes",keyword:"x-lightnvr-max-bytes",params:{},message:"must fit the UTF-8 byte limit"}],!1;if(e!==""){let l,t=!0;try{l=JSON.parse(e)}catch{t=!1}if(t){let n=r;u(l,{instancePath:i,parentData:e,parentDataProperty:d,rootData:m,dynamicAnchors:f})||(s=s===null?u.errors:s.concat(u.errors),r=s.length);var a=n===r;t=a}if(!t)return F.errors=[{instancePath:i,schemaPath:"#/contentSchema",keyword:"contentSchema",params:{},message:"must contain valid JSON matching contentSchema (or be empty)"}],!1}}else return F.errors=[{instancePath:i,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return F.errors=s,r===0}F.evaluated={dynamicProps:!1,dynamicItems:!1};function b(e,{instancePath:i="",parentData:_,parentDataProperty:d,rootData:m=e,dynamicAnchors:f={}}={}){let s=null,r=0,o=b.evaluated;o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0);let a=r;c(e,{instancePath:i,parentData:_,parentDataProperty:d,rootData:m,dynamicAnchors:f})||(s=s===null?c.errors:s.concat(c.errors),r=s.length);var l=a===r;if(l){let T=r,D=r,A=r;if(e&&typeof e=="object"&&!Array.isArray(e)){let v;if(e.admin_url===void 0&&(v="admin_url")){let p={};s===null?s=[p]:s.push(p),r++}}var t=A===r;if(t)return b.errors=[{instancePath:i,schemaPath:"#/allOf/1/not",keyword:"not",params:{},message:"must NOT be valid"}],!1;if(r=D,s!==null&&(D?s.length=D:s=null),r===T)if(e&&typeof e=="object"&&!Array.isArray(e)){let v;if(e.streaming_enabled===void 0&&(v="streaming_enabled")||e.playback_transport===void 0&&(v="playback_transport")||e.eptz_config===void 0&&(v="eptz_config")||e.detection_model===void 0&&(v="detection_model")||e.privacy_mode===void 0&&(v="privacy_mode")||e.can_control_privacy===void 0&&(v="can_control_privacy")||e.has_sub_stream===void 0&&(v="has_sub_stream")||e.ptz_enabled===void 0&&(v="ptz_enabled")||e.backchannel_enabled===void 0&&(v="backchannel_enabled")||e.go2rtc_hls_available===void 0&&(v="go2rtc_hls_available"))return b.errors=[{instancePath:i,schemaPath:"#/allOf/1/required",keyword:"required",params:{missingProperty:v},message:"must have required property '"+v+"'"}],!1;if(e.streaming_enabled!==void 0){let p=r;if(typeof e.streaming_enabled!="boolean")return b.errors=[{instancePath:i+"/streaming_enabled",schemaPath:"#/allOf/1/properties/streaming_enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=p===r}else var n=!0;if(n){if(e.playback_transport!==void 0){let p=r;I(e.playback_transport,{instancePath:i+"/playback_transport",parentData:e,parentDataProperty:"playback_transport",rootData:m,dynamicAnchors:f})||(s=s===null?I.errors:s.concat(I.errors),r=s.length);var n=p===r}else var n=!0;if(n){if(e.eptz_config!==void 0){let p=r;F(e.eptz_config,{instancePath:i+"/eptz_config",parentData:e,parentDataProperty:"eptz_config",rootData:m,dynamicAnchors:f})||(s=s===null?F.errors:s.concat(F.errors),r=s.length);var n=p===r}else var n=!0;if(n){if(e.detection_model!==void 0){let p=e.detection_model,L=r;if(r===L)if(typeof p=="string"){if(O(p)>511)return b.errors=[{instancePath:i+"/detection_model",schemaPath:"#/allOf/1/properties/detection_model/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return b.errors=[{instancePath:i+"/detection_model",schemaPath:"#/allOf/1/properties/detection_model/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var n=L===r}else var n=!0;if(n){if(e.privacy_mode!==void 0){let p=r;if(typeof e.privacy_mode!="boolean")return b.errors=[{instancePath:i+"/privacy_mode",schemaPath:"#/allOf/1/properties/privacy_mode/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=p===r}else var n=!0;if(n){if(e.can_control_privacy!==void 0){let p=r;if(typeof e.can_control_privacy!="boolean")return b.errors=[{instancePath:i+"/can_control_privacy",schemaPath:"#/allOf/1/properties/can_control_privacy/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=p===r}else var n=!0;if(n){if(e.has_sub_stream!==void 0){let p=r;if(typeof e.has_sub_stream!="boolean")return b.errors=[{instancePath:i+"/has_sub_stream",schemaPath:"#/allOf/1/properties/has_sub_stream/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=p===r}else var n=!0;if(n){if(e.ptz_enabled!==void 0){let p=r;if(typeof e.ptz_enabled!="boolean")return b.errors=[{instancePath:i+"/ptz_enabled",schemaPath:"#/allOf/1/properties/ptz_enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=p===r}else var n=!0;if(n){if(e.backchannel_enabled!==void 0){let p=r;if(typeof e.backchannel_enabled!="boolean")return b.errors=[{instancePath:i+"/backchannel_enabled",schemaPath:"#/allOf/1/properties/backchannel_enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=p===r}else var n=!0;if(n)if(e.go2rtc_hls_available!==void 0){let p=r;if(typeof e.go2rtc_hls_available!="boolean")return b.errors=[{instancePath:i+"/go2rtc_hls_available",schemaPath:"#/allOf/1/properties/go2rtc_hls_available/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var n=p===r}else var n=!0}}}}}}}}}else return b.errors=[{instancePath:i,schemaPath:"#/allOf/1/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;var l=T===r}return b.errors=s,r===0}b.evaluated={props:{streaming_enabled:!0,playback_transport:!0,eptz_config:!0,detection_model:!0,privacy_mode:!0,can_control_privacy:!0,has_sub_stream:!0,ptz_enabled:!0,backchannel_enabled:!0,go2rtc_hls_available:!0,camera_uuid:!0,name:!0,location_uuid:!0,location_name:!0,location_path:!0,enabled:!0,width:!0,height:!0,fps:!0,codec:!0,protocol:!0,record:!0,record_on_schedule:!0,detection_based_recording:!0,isOnvif:!0,tags:!0,status:!0,availability:!0,first_video_at:!0,last_frame_ts:!0,current_fps:!0,recording_active:!0,last_recording_at:!0,can_configure:!0},dynamicProps:!1,dynamicItems:!1};function j(e,{instancePath:i="",parentData:_,parentDataProperty:d,rootData:m=e,dynamicAnchors:f={}}={}){let s=null,r=0,o=j.evaluated;o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0);let a=r;g(e,{instancePath:i,parentData:_,parentDataProperty:d,rootData:m,dynamicAnchors:f})||(s=s===null?g.errors:s.concat(g.errors),r=s.length);var l=a===r;if(l){let n=r;if(r===n)if(e&&typeof e=="object"&&!Array.isArray(e)){let D;if(e.streams===void 0&&(D="streams"))return j.errors=[{instancePath:i,schemaPath:"#/allOf/1/required",keyword:"required",params:{missingProperty:D},message:"must have required property '"+D+"'"}],!1;if(e.streams!==void 0){let A=e.streams;if(r===r)if(Array.isArray(A)){if(A.length>100)return j.errors=[{instancePath:i+"/streams",schemaPath:"#/allOf/1/properties/streams/maxItems",keyword:"maxItems",params:{limit:100},message:"must NOT have more than 100 items"}],!1;{var t=!0;let v=A.length;for(let p=0;p()=>{try{return a||e((a={exports:{}}).exports,a),a.exports}catch(f){throw a=0,f}};var k=N(P=>{"use strict";Object.defineProperty(P,"__esModule",{value:!0});function w(e){let a=e.length,f=0,l=0,p;for(;l=55296&&p<=56319&&l=",limit:0},message:"must be >= 0"}],!1;var o=m===r}else var o=!0;if(o){if(e.page!==void 0){let t=e.page,m=r;if(!(typeof t=="number"&&!(t%1)&&!isNaN(t)&&isFinite(t)))return n.errors=[{instancePath:a+"/page",schemaPath:"#/properties/page/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;if(r===m&&typeof t=="number"&&isFinite(t)){if(t>1e6||isNaN(t))return n.errors=[{instancePath:a+"/page",schemaPath:"#/properties/page/maximum",keyword:"maximum",params:{comparison:"<=",limit:1e6},message:"must be <= 1000000"}],!1;if(t<1||isNaN(t))return n.errors=[{instancePath:a+"/page",schemaPath:"#/properties/page/minimum",keyword:"minimum",params:{comparison:">=",limit:1},message:"must be >= 1"}],!1}var o=m===r}else var o=!0;if(o){if(e.page_size!==void 0){let t=e.page_size,m=r;if(!(typeof t=="number"&&!(t%1)&&!isNaN(t)&&isFinite(t)))return n.errors=[{instancePath:a+"/page_size",schemaPath:"#/properties/page_size/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;if(r===m&&typeof t=="number"&&isFinite(t)){if(t>100||isNaN(t))return n.errors=[{instancePath:a+"/page_size",schemaPath:"#/properties/page_size/maximum",keyword:"maximum",params:{comparison:"<=",limit:100},message:"must be <= 100"}],!1;if(t<1||isNaN(t))return n.errors=[{instancePath:a+"/page_size",schemaPath:"#/properties/page_size/minimum",keyword:"minimum",params:{comparison:">=",limit:1},message:"must be >= 1"}],!1}var o=m===r}else var o=!0;if(o)if(e.total_pages!==void 0){let t=e.total_pages,m=r;if(!(typeof t=="number"&&!(t%1)&&!isNaN(t)&&isFinite(t)))return n.errors=[{instancePath:a+"/total_pages",schemaPath:"#/properties/total_pages/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;if(r===m&&typeof t=="number"&&isFinite(t)&&(t<0||isNaN(t)))return n.errors=[{instancePath:a+"/total_pages",schemaPath:"#/properties/total_pages/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1;var o=m===r}else var o=!0}}}else return n.errors=[{instancePath:a,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return n.errors=s,r===0}n.evaluated={props:{total:!0,page:!0,page_size:!0,total_pages:!0},dynamicProps:!1,dynamicItems:!1};var A=k().default,D=new RegExp("^(|[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})$","u");function d(e,{instancePath:a="",parentData:f,parentDataProperty:l,rootData:p=e,dynamicAnchors:g={}}={}){let s=null,r=0,i=d.evaluated;if(i.dynamicProps&&(i.props=void 0),i.dynamicItems&&(i.items=void 0),r===0)if(typeof e=="string"){if(A(e)>36)return d.errors=[{instancePath:a,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:36},message:"must NOT have more than 36 characters"}],!1;if(!D.test(e))return d.errors=[{instancePath:a,schemaPath:"#/pattern",keyword:"pattern",params:{pattern:"^(|[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})$"},message:'must match pattern "^(|[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})$"'}],!1}else return d.errors=[{instancePath:a,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return d.errors=s,r===0}d.evaluated={dynamicProps:!1,dynamicItems:!1};function c(e,{instancePath:a="",parentData:f,parentDataProperty:l,rootData:p=e,dynamicAnchors:g={}}={}){let s=null,r=0,i=c.evaluated;if(i.dynamicProps&&(i.props=void 0),i.dynamicItems&&(i.items=void 0),r===0)if(typeof e=="string"){if(A(e)>255)return c.errors=[{instancePath:a,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1;if(A(e)<1)return c.errors=[{instancePath:a,schemaPath:"#/minLength",keyword:"minLength",params:{limit:1},message:"must NOT have fewer than 1 characters"}],!1;if(new TextEncoder().encode(e).byteLength>255)return c.errors=[{instancePath:a,schemaPath:"#/x-lightnvr-max-bytes",keyword:"x-lightnvr-max-bytes",params:{},message:"must fit the UTF-8 byte limit"}],!1}else return c.errors=[{instancePath:a,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return c.errors=s,r===0}c.evaluated={dynamicProps:!1,dynamicItems:!1};function y(e,{instancePath:a="",parentData:f,parentDataProperty:l,rootData:p=e,dynamicAnchors:g={}}={}){let s=null,r=0,i=y.evaluated;if(i.dynamicProps&&(i.props=void 0),i.dynamicItems&&(i.items=void 0),r===0)if(e&&typeof e=="object"&&!Array.isArray(e)){let u;if(e.camera_uuid===void 0&&(u="camera_uuid")||e.name===void 0&&(u="name"))return y.errors=[{instancePath:a,schemaPath:"#/required",keyword:"required",params:{missingProperty:u},message:"must have required property '"+u+"'"}],!1;{let t=r;for(let m in e)if(!(m==="camera_uuid"||m==="name")){return y.errors=[{instancePath:a,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty:m},message:"must NOT have additional properties"}],!1;break}if(t===r){if(e.camera_uuid!==void 0){let m=r;d(e.camera_uuid,{instancePath:a+"/camera_uuid",parentData:e,parentDataProperty:"camera_uuid",rootData:p,dynamicAnchors:g})||(s=s===null?d.errors:s.concat(d.errors),r=s.length);var o=m===r}else var o=!0;if(o)if(e.name!==void 0){let m=r;c(e.name,{instancePath:a+"/name",parentData:e,parentDataProperty:"name",rootData:p,dynamicAnchors:g})||(s=s===null?c.errors:s.concat(c.errors),r=s.length);var o=m===r}else var o=!0}}}else return y.errors=[{instancePath:a,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return y.errors=s,r===0}y.evaluated={props:!0,dynamicProps:!1,dynamicItems:!1};function h(e,{instancePath:a="",parentData:f,parentDataProperty:l,rootData:p=e,dynamicAnchors:g={}}={}){let s=null,r=0,i=h.evaluated;i.dynamicProps&&(i.props=void 0),i.dynamicItems&&(i.items=void 0);let o=r;n(e,{instancePath:a,parentData:f,parentDataProperty:l,rootData:p,dynamicAnchors:g})||(s=s===null?n.errors:s.concat(n.errors),r=s.length);var u=o===r;if(u){let m=r;if(r===m)if(e&&typeof e=="object"&&!Array.isArray(e)){let b;if(e.streams===void 0&&(b="streams"))return h.errors=[{instancePath:a,schemaPath:"#/allOf/1/required",keyword:"required",params:{missingProperty:b},message:"must have required property '"+b+"'"}],!1;if(e.streams!==void 0){let v=e.streams;if(r===r)if(Array.isArray(v)){if(v.length>100)return h.errors=[{instancePath:a+"/streams",schemaPath:"#/allOf/1/properties/streams/maxItems",keyword:"maxItems",params:{limit:100},message:"must NOT have more than 100 items"}],!1;{var t=!0;let F=v.length;for(let _=0;_()=>{try{return n||e((n={exports:{}}).exports,n),n.exports}catch(_){throw n=0,_}};var T=L(D=>{"use strict";Object.defineProperty(D,"__esModule",{value:!0});function N(e){let n=e.length,_=0,y=0,m;for(;y=55296&&m<=56319&&y1||isNaN(t))return f.errors=[{instancePath:n+"/centerX",schemaPath:"#/properties/centerX/maximum",keyword:"maximum",params:{comparison:"<=",limit:1},message:"must be <= 1"}],!1;if(t<0||isNaN(t))return f.errors=[{instancePath:n+"/centerX",schemaPath:"#/properties/centerX/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1}else return f.errors=[{instancePath:n+"/centerX",schemaPath:"#/properties/centerX/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var s=a===r}else var s=!0;if(s){if(e.centerY!==void 0){let t=e.centerY,a=r;if(r===a)if(typeof t=="number"&&isFinite(t)){if(t>1||isNaN(t))return f.errors=[{instancePath:n+"/centerY",schemaPath:"#/properties/centerY/maximum",keyword:"maximum",params:{comparison:"<=",limit:1},message:"must be <= 1"}],!1;if(t<0||isNaN(t))return f.errors=[{instancePath:n+"/centerY",schemaPath:"#/properties/centerY/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1}else return f.errors=[{instancePath:n+"/centerY",schemaPath:"#/properties/centerY/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var s=a===r}else var s=!0;if(s){if(e.radius!==void 0){let t=e.radius,a=r;if(r===a)if(typeof t=="number"&&isFinite(t)){if(t>1||isNaN(t))return f.errors=[{instancePath:n+"/radius",schemaPath:"#/properties/radius/maximum",keyword:"maximum",params:{comparison:"<=",limit:1},message:"must be <= 1"}],!1;if(t<.05||isNaN(t))return f.errors=[{instancePath:n+"/radius",schemaPath:"#/properties/radius/minimum",keyword:"minimum",params:{comparison:">=",limit:.05},message:"must be >= 0.05"}],!1}else return f.errors=[{instancePath:n+"/radius",schemaPath:"#/properties/radius/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var s=a===r}else var s=!0;if(s){if(e.fov!==void 0){let t=e.fov,a=r;if(r===a)if(typeof t=="number"&&isFinite(t)){if(t>360||isNaN(t))return f.errors=[{instancePath:n+"/fov",schemaPath:"#/properties/fov/maximum",keyword:"maximum",params:{comparison:"<=",limit:360},message:"must be <= 360"}],!1;if(t<160||isNaN(t))return f.errors=[{instancePath:n+"/fov",schemaPath:"#/properties/fov/minimum",keyword:"minimum",params:{comparison:">=",limit:160},message:"must be >= 160"}],!1}else return f.errors=[{instancePath:n+"/fov",schemaPath:"#/properties/fov/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var s=a===r}else var s=!0;if(s){if(e.rotation!==void 0){let t=e.rotation,a=r;if(r===a)if(typeof t=="number"&&isFinite(t)){if(t>360||isNaN(t))return f.errors=[{instancePath:n+"/rotation",schemaPath:"#/properties/rotation/maximum",keyword:"maximum",params:{comparison:"<=",limit:360},message:"must be <= 360"}],!1;if(t<-360||isNaN(t))return f.errors=[{instancePath:n+"/rotation",schemaPath:"#/properties/rotation/minimum",keyword:"minimum",params:{comparison:">=",limit:-360},message:"must be >= -360"}],!1}else return f.errors=[{instancePath:n+"/rotation",schemaPath:"#/properties/rotation/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var s=a===r}else var s=!0;if(s){if(e.defaultYaw!==void 0){let t=e.defaultYaw,a=r;if(r===a)if(typeof t=="number"&&isFinite(t)){if(t>360||isNaN(t))return f.errors=[{instancePath:n+"/defaultYaw",schemaPath:"#/properties/defaultYaw/maximum",keyword:"maximum",params:{comparison:"<=",limit:360},message:"must be <= 360"}],!1;if(t<-360||isNaN(t))return f.errors=[{instancePath:n+"/defaultYaw",schemaPath:"#/properties/defaultYaw/minimum",keyword:"minimum",params:{comparison:">=",limit:-360},message:"must be >= -360"}],!1}else return f.errors=[{instancePath:n+"/defaultYaw",schemaPath:"#/properties/defaultYaw/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var s=a===r}else var s=!0;if(s){if(e.defaultTilt!==void 0){let t=e.defaultTilt,a=r;if(r===a)if(typeof t=="number"&&isFinite(t)){if(t>30||isNaN(t))return f.errors=[{instancePath:n+"/defaultTilt",schemaPath:"#/properties/defaultTilt/maximum",keyword:"maximum",params:{comparison:"<=",limit:30},message:"must be <= 30"}],!1;if(t<-90||isNaN(t))return f.errors=[{instancePath:n+"/defaultTilt",schemaPath:"#/properties/defaultTilt/minimum",keyword:"minimum",params:{comparison:">=",limit:-90},message:"must be >= -90"}],!1}else return f.errors=[{instancePath:n+"/defaultTilt",schemaPath:"#/properties/defaultTilt/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var s=a===r}else var s=!0;if(s)if(e.defaultViewFov!==void 0){let t=e.defaultViewFov,a=r;if(r===a)if(typeof t=="number"&&isFinite(t)){if(t>120||isNaN(t))return f.errors=[{instancePath:n+"/defaultViewFov",schemaPath:"#/properties/defaultViewFov/maximum",keyword:"maximum",params:{comparison:"<=",limit:120},message:"must be <= 120"}],!1;if(t<20||isNaN(t))return f.errors=[{instancePath:n+"/defaultViewFov",schemaPath:"#/properties/defaultViewFov/minimum",keyword:"minimum",params:{comparison:">=",limit:20},message:"must be >= 20"}],!1}else return f.errors=[{instancePath:n+"/defaultViewFov",schemaPath:"#/properties/defaultViewFov/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var s=a===r}else var s=!0}}}}}}}}}}else return f.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return f.errors=i,r===0}f.evaluated={props:{version:!0,projection:!0,mount:!0,centerX:!0,centerY:!0,radius:!0,fov:!0,rotation:!0,defaultYaw:!0,defaultTilt:!0,defaultViewFov:!0},dynamicProps:!1,dynamicItems:!1};function b(e,{instancePath:n="",parentData:_,parentDataProperty:y,rootData:m=e,dynamicAnchors:p={}}={}){let i=null,r=0,o=b.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(typeof e=="string"){if(h(e)>1023)return b.errors=[{instancePath:n,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:1023},message:"must NOT have more than 1023 characters"}],!1;if(new TextEncoder().encode(e).byteLength>1023)return b.errors=[{instancePath:n,schemaPath:"#/x-lightnvr-max-bytes",keyword:"x-lightnvr-max-bytes",params:{},message:"must fit the UTF-8 byte limit"}],!1;if(e!==""){let c,t=!0;try{c=JSON.parse(e)}catch{t=!1}if(t){let a=r;f(c,{instancePath:n,parentData:e,parentDataProperty:y,rootData:m,dynamicAnchors:p})||(i=i===null?f.errors:i.concat(f.errors),r=i.length);var s=a===r;t=s}if(!t)return b.errors=[{instancePath:n,schemaPath:"#/contentSchema",keyword:"contentSchema",params:{},message:"must contain valid JSON matching contentSchema (or be empty)"}],!1}}else return b.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return b.errors=i,r===0}b.evaluated={dynamicProps:!1,dynamicItems:!1};function u(e,{instancePath:n="",parentData:_,parentDataProperty:y,rootData:m=e,dynamicAnchors:p={}}={}){let i=null,r=0,o=u.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),!(typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)))return u.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;if(r===0&&typeof e=="number"&&isFinite(e)){if(e>2147483647||isNaN(e))return u.errors=[{instancePath:n,schemaPath:"#/maximum",keyword:"maximum",params:{comparison:"<=",limit:2147483647},message:"must be <= 2147483647"}],!1;if(e<-2147483648||isNaN(e))return u.errors=[{instancePath:n,schemaPath:"#/minimum",keyword:"minimum",params:{comparison:">=",limit:-2147483648},message:"must be >= -2147483648"}],!1}return u.errors=i,r===0}u.evaluated={dynamicProps:!1,dynamicItems:!1};var I={type:"integer",enum:[0,1],$id:"urn:lightnvr:contract:StreamProtocol"};function k(e,{instancePath:n="",parentData:_,parentDataProperty:y,rootData:m=e,dynamicAnchors:p={}}={}){let i=null,r=0,o=k.evaluated;return o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)?e===0||e===1?(k.errors=i,r===0):(k.errors=[{instancePath:n,schemaPath:"#/enum",keyword:"enum",params:{allowedValues:I.enum},message:"must be equal to one of the allowed values"}],!1):(k.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1)}k.evaluated={dynamicProps:!1,dynamicItems:!1};function g(e,{instancePath:n="",parentData:_,parentDataProperty:y,rootData:m=e,dynamicAnchors:p={}}={}){let i=null,r=0,o=g.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),!(typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)))return g.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;if(r===0&&typeof e=="number"&&isFinite(e)){if(e>365||isNaN(e))return g.errors=[{instancePath:n,schemaPath:"#/maximum",keyword:"maximum",params:{comparison:"<=",limit:365},message:"must be <= 365"}],!1;if(e<-1||isNaN(e))return g.errors=[{instancePath:n,schemaPath:"#/minimum",keyword:"minimum",params:{comparison:">=",limit:-1},message:"must be >= -1"}],!1}return g.errors=i,r===0}g.evaluated={dynamicProps:!1,dynamicItems:!1};function v(e,{instancePath:n="",parentData:_,parentDataProperty:y,rootData:m=e,dynamicAnchors:p={}}={}){let i=null,r=0,o=v.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(Array.isArray(e)){if(e.length>168)return v.errors=[{instancePath:n,schemaPath:"#/maxItems",keyword:"maxItems",params:{limit:168},message:"must NOT have more than 168 items"}],!1;if(e.length<168)return v.errors=[{instancePath:n,schemaPath:"#/minItems",keyword:"minItems",params:{limit:168},message:"must NOT have fewer than 168 items"}],!1;{var s=!0;let c=e.length;for(let t=0;t511)return x.errors=[{instancePath:n,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1;if(F.test(e)){if(new TextEncoder().encode(e).byteLength>511)return x.errors=[{instancePath:n,schemaPath:"#/x-lightnvr-max-bytes",keyword:"x-lightnvr-max-bytes",params:{},message:"must fit the UTF-8 byte limit"}],!1}else return x.errors=[{instancePath:n,schemaPath:"#/pattern",keyword:"pattern",params:{pattern:"^($|([Hh][Tt][Tt][Pp][Ss]?|[Rr][Tt][Ss][Pp][Ss]?)://)"},message:'must match pattern "^($|([Hh][Tt][Tt][Pp][Ss]?|[Rr][Tt][Ss][Pp][Ss]?)://)"'}],!1}return x.errors=i,r===0}x.evaluated={dynamicProps:!1,dynamicItems:!1};var Y={type:["string","null"],maxLength:511,pattern:"^[\\t\\n\\v\\f\\r ,]*(([Rr][Tt][Mm][Pp][Ss]?://[!#-&(-+\\x2D-\\x5B\\x5D-~]+)([\\t\\n\\v\\f\\r ,]+[Rr][Tt][Mm][Pp][Ss]?://[!#-&(-+\\x2D-\\x5B\\x5D-~]+)*)?[\\t\\n\\v\\f\\r ,]*$","x-lightnvr-max-bytes":511,$id:"urn:lightnvr:contract:PublishUrlList"},E=new RegExp("^[\\t\\n\\v\\f\\r ,]*(([Rr][Tt][Mm][Pp][Ss]?://[!#-&(-+\\x2D-\\x5B\\x5D-~]+)([\\t\\n\\v\\f\\r ,]+[Rr][Tt][Mm][Pp][Ss]?://[!#-&(-+\\x2D-\\x5B\\x5D-~]+)*)?[\\t\\n\\v\\f\\r ,]*$","u");function P(e,{instancePath:n="",parentData:_,parentDataProperty:y,rootData:m=e,dynamicAnchors:p={}}={}){let i=null,r=0,o=P.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),typeof e!="string"&&e!==null)return P.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:Y.type},message:"must be string,null"}],!1;if(r===0&&typeof e=="string"){if(h(e)>511)return P.errors=[{instancePath:n,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1;if(E.test(e)){if(new TextEncoder().encode(e).byteLength>511)return P.errors=[{instancePath:n,schemaPath:"#/x-lightnvr-max-bytes",keyword:"x-lightnvr-max-bytes",params:{},message:"must fit the UTF-8 byte limit"}],!1}else return P.errors=[{instancePath:n,schemaPath:"#/pattern",keyword:"pattern",params:{pattern:"^[\\t\\n\\v\\f\\r ,]*(([Rr][Tt][Mm][Pp][Ss]?://[!#-&(-+\\x2D-\\x5B\\x5D-~]+)([\\t\\n\\v\\f\\r ,]+[Rr][Tt][Mm][Pp][Ss]?://[!#-&(-+\\x2D-\\x5B\\x5D-~]+)*)?[\\t\\n\\v\\f\\r ,]*$"},message:'must match pattern "^[\\t\\n\\v\\f\\r ,]*(([Rr][Tt][Mm][Pp][Ss]?://[!#-&(-+\\x2D-\\x5B\\x5D-~]+)([\\t\\n\\v\\f\\r ,]+[Rr][Tt][Mm][Pp][Ss]?://[!#-&(-+\\x2D-\\x5B\\x5D-~]+)*)?[\\t\\n\\v\\f\\r ,]*$"'}],!1}return P.errors=i,r===0}P.evaluated={dynamicProps:!1,dynamicItems:!1};function l(e,{instancePath:n="",parentData:_,parentDataProperty:y,rootData:m=e,dynamicAnchors:p={}}={}){let i=null,r=0,o=l.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),r===0)if(e&&typeof e=="object"&&!Array.isArray(e)){let c=r;for(let t in e)if(!O.call($.properties,t)){return l.errors=[{instancePath:n,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty:t},message:"must NOT have additional properties"}],!1;break}if(c===r){if(e.url!==void 0){let t=e.url,a=r;if(r===a)if(typeof t=="string"){if(h(t)>511)return l.errors=[{instancePath:n+"/url",schemaPath:"#/properties/url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1}else return l.errors=[{instancePath:n+"/url",schemaPath:"#/properties/url/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var s=a===r}else var s=!0;if(s){if(e.enabled!==void 0){let t=r;if(typeof e.enabled!="boolean")return l.errors=[{instancePath:n+"/enabled",schemaPath:"#/properties/enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var s=t===r}else var s=!0;if(s){if(e.enable_disabled!==void 0){let t=r;if(typeof e.enable_disabled!="boolean")return l.errors=[{instancePath:n+"/enable_disabled",schemaPath:"#/properties/enable_disabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var s=t===r}else var s=!0;if(s){if(e.streaming_enabled!==void 0){let t=r;if(typeof e.streaming_enabled!="boolean")return l.errors=[{instancePath:n+"/streaming_enabled",schemaPath:"#/properties/streaming_enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var s=t===r}else var s=!0;if(s){if(e.playback_transport!==void 0){let t=r;w(e.playback_transport,{instancePath:n+"/playback_transport",parentData:e,parentDataProperty:"playback_transport",rootData:m,dynamicAnchors:p})||(i=i===null?w.errors:i.concat(w.errors),r=i.length);var s=t===r}else var s=!0;if(s){if(e.eptz_config!==void 0){let t=r;b(e.eptz_config,{instancePath:n+"/eptz_config",parentData:e,parentDataProperty:"eptz_config",rootData:m,dynamicAnchors:p})||(i=i===null?b.errors:i.concat(b.errors),r=i.length);var s=t===r}else var s=!0;if(s){if(e.codec!==void 0){let t=e.codec,a=r;if(r===a)if(typeof t=="string"){if(h(t)>15)return l.errors=[{instancePath:n+"/codec",schemaPath:"#/properties/codec/maxLength",keyword:"maxLength",params:{limit:15},message:"must NOT have more than 15 characters"}],!1}else return l.errors=[{instancePath:n+"/codec",schemaPath:"#/properties/codec/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var s=a===r}else var s=!0;if(s){if(e.priority!==void 0){let t=r;u(e.priority,{instancePath:n+"/priority",parentData:e,parentDataProperty:"priority",rootData:m,dynamicAnchors:p})||(i=i===null?u.errors:i.concat(u.errors),r=i.length);var s=t===r}else var s=!0;if(s){if(e.protocol!==void 0){let t=r;k(e.protocol,{instancePath:n+"/protocol",parentData:e,parentDataProperty:"protocol",rootData:m,dynamicAnchors:p})||(i=i===null?k.errors:i.concat(k.errors),r=i.length);var s=t===r}else var s=!0;if(s){if(e.segment_duration!==void 0){let t=r;u(e.segment_duration,{instancePath:n+"/segment_duration",parentData:e,parentDataProperty:"segment_duration",rootData:m,dynamicAnchors:p})||(i=i===null?u.errors:i.concat(u.errors),r=i.length);var s=t===r}else var s=!0;if(s){if(e.record!==void 0){let t=r;if(typeof e.record!="boolean")return l.errors=[{instancePath:n+"/record",schemaPath:"#/properties/record/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var s=t===r}else var s=!0;if(s){if(e.detection_based_recording!==void 0){let t=r;if(typeof e.detection_based_recording!="boolean")return l.errors=[{instancePath:n+"/detection_based_recording",schemaPath:"#/properties/detection_based_recording/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var s=t===r}else var s=!0;if(s){if(e.detection_model!==void 0){let t=e.detection_model,a=r;if(r===a)if(typeof t=="string"){if(h(t)>255)return l.errors=[{instancePath:n+"/detection_model",schemaPath:"#/properties/detection_model/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1;if(new TextEncoder().encode(t).byteLength>255)return l.errors=[{instancePath:n+"/detection_model",schemaPath:"#/properties/detection_model/x-lightnvr-max-bytes",keyword:"x-lightnvr-max-bytes",params:{},message:"must fit the UTF-8 byte limit"}],!1}else return l.errors=[{instancePath:n+"/detection_model",schemaPath:"#/properties/detection_model/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var s=a===r}else var s=!0;if(s){if(e.detection_threshold!==void 0){let t=e.detection_threshold,a=r;if(!(typeof t=="number"&&isFinite(t)))return l.errors=[{instancePath:n+"/detection_threshold",schemaPath:"#/properties/detection_threshold/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var s=a===r}else var s=!0;if(s){if(e.detection_interval!==void 0){let t=r;u(e.detection_interval,{instancePath:n+"/detection_interval",parentData:e,parentDataProperty:"detection_interval",rootData:m,dynamicAnchors:p})||(i=i===null?u.errors:i.concat(u.errors),r=i.length);var s=t===r}else var s=!0;if(s){if(e.pre_detection_buffer!==void 0){let t=r;u(e.pre_detection_buffer,{instancePath:n+"/pre_detection_buffer",parentData:e,parentDataProperty:"pre_detection_buffer",rootData:m,dynamicAnchors:p})||(i=i===null?u.errors:i.concat(u.errors),r=i.length);var s=t===r}else var s=!0;if(s){if(e.post_detection_buffer!==void 0){let t=r;u(e.post_detection_buffer,{instancePath:n+"/post_detection_buffer",parentData:e,parentDataProperty:"post_detection_buffer",rootData:m,dynamicAnchors:p})||(i=i===null?u.errors:i.concat(u.errors),r=i.length);var s=t===r}else var s=!0;if(s){if(e.detection_object_filter!==void 0){let t=e.detection_object_filter,a=r;if(r===a)if(typeof t=="string"){if(h(t)>15)return l.errors=[{instancePath:n+"/detection_object_filter",schemaPath:"#/properties/detection_object_filter/maxLength",keyword:"maxLength",params:{limit:15},message:"must NOT have more than 15 characters"}],!1}else return l.errors=[{instancePath:n+"/detection_object_filter",schemaPath:"#/properties/detection_object_filter/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var s=a===r}else var s=!0;if(s){if(e.detection_object_filter_list!==void 0){let t=e.detection_object_filter_list,a=r;if(r===a)if(typeof t=="string"){if(h(t)>255)return l.errors=[{instancePath:n+"/detection_object_filter_list",schemaPath:"#/properties/detection_object_filter_list/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1}else return l.errors=[{instancePath:n+"/detection_object_filter_list",schemaPath:"#/properties/detection_object_filter_list/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var s=a===r}else var s=!0;if(s){if(e.record_audio!==void 0){let t=r;if(typeof e.record_audio!="boolean")return l.errors=[{instancePath:n+"/record_audio",schemaPath:"#/properties/record_audio/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var s=t===r}else var s=!0;if(s){if(e.audio_voice_enhancement!==void 0){let t=r;if(typeof e.audio_voice_enhancement!="boolean")return l.errors=[{instancePath:n+"/audio_voice_enhancement",schemaPath:"#/properties/audio_voice_enhancement/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var s=t===r}else var s=!0;if(s){if(e.isOnvif!==void 0){let t=r;if(typeof e.isOnvif!="boolean")return l.errors=[{instancePath:n+"/isOnvif",schemaPath:"#/properties/isOnvif/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var s=t===r}else var s=!0;if(s){if(e.backchannel_enabled!==void 0){let t=r;if(typeof e.backchannel_enabled!="boolean")return l.errors=[{instancePath:n+"/backchannel_enabled",schemaPath:"#/properties/backchannel_enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var s=t===r}else var s=!0;if(s){if(e.onvif_username!==void 0){let t=e.onvif_username,a=r;if(r===a)if(typeof t=="string"){if(h(t)>63)return l.errors=[{instancePath:n+"/onvif_username",schemaPath:"#/properties/onvif_username/maxLength",keyword:"maxLength",params:{limit:63},message:"must NOT have more than 63 characters"}],!1}else return l.errors=[{instancePath:n+"/onvif_username",schemaPath:"#/properties/onvif_username/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var s=a===r}else var s=!0;if(s){if(e.onvif_password!==void 0){let t=e.onvif_password,a=r;if(r===a)if(typeof t=="string"){if(h(t)>63)return l.errors=[{instancePath:n+"/onvif_password",schemaPath:"#/properties/onvif_password/maxLength",keyword:"maxLength",params:{limit:63},message:"must NOT have more than 63 characters"}],!1}else return l.errors=[{instancePath:n+"/onvif_password",schemaPath:"#/properties/onvif_password/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var s=a===r}else var s=!0;if(s){if(e.onvif_profile!==void 0){let t=e.onvif_profile,a=r;if(r===a)if(typeof t=="string"){if(h(t)>63)return l.errors=[{instancePath:n+"/onvif_profile",schemaPath:"#/properties/onvif_profile/maxLength",keyword:"maxLength",params:{limit:63},message:"must NOT have more than 63 characters"}],!1}else return l.errors=[{instancePath:n+"/onvif_profile",schemaPath:"#/properties/onvif_profile/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var s=a===r}else var s=!0;if(s){if(e.onvif_port!==void 0){let t=r;u(e.onvif_port,{instancePath:n+"/onvif_port",parentData:e,parentDataProperty:"onvif_port",rootData:m,dynamicAnchors:p})||(i=i===null?u.errors:i.concat(u.errors),r=i.length);var s=t===r}else var s=!0;if(s){if(e.retention_days!==void 0){let t=r;g(e.retention_days,{instancePath:n+"/retention_days",parentData:e,parentDataProperty:"retention_days",rootData:m,dynamicAnchors:p})||(i=i===null?g.errors:i.concat(g.errors),r=i.length);var s=t===r}else var s=!0;if(s){if(e.detection_retention_days!==void 0){let t=r;g(e.detection_retention_days,{instancePath:n+"/detection_retention_days",parentData:e,parentDataProperty:"detection_retention_days",rootData:m,dynamicAnchors:p})||(i=i===null?g.errors:i.concat(g.errors),r=i.length);var s=t===r}else var s=!0;if(s){if(e.max_storage_mb!==void 0){let t=r;u(e.max_storage_mb,{instancePath:n+"/max_storage_mb",parentData:e,parentDataProperty:"max_storage_mb",rootData:m,dynamicAnchors:p})||(i=i===null?u.errors:i.concat(u.errors),r=i.length);var s=t===r}else var s=!0;if(s){if(e.tier_critical_multiplier!==void 0){let t=e.tier_critical_multiplier,a=r;if(!(typeof t=="number"&&isFinite(t)))return l.errors=[{instancePath:n+"/tier_critical_multiplier",schemaPath:"#/properties/tier_critical_multiplier/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var s=a===r}else var s=!0;if(s){if(e.tier_important_multiplier!==void 0){let t=e.tier_important_multiplier,a=r;if(!(typeof t=="number"&&isFinite(t)))return l.errors=[{instancePath:n+"/tier_important_multiplier",schemaPath:"#/properties/tier_important_multiplier/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var s=a===r}else var s=!0;if(s){if(e.tier_ephemeral_multiplier!==void 0){let t=e.tier_ephemeral_multiplier,a=r;if(!(typeof t=="number"&&isFinite(t)))return l.errors=[{instancePath:n+"/tier_ephemeral_multiplier",schemaPath:"#/properties/tier_ephemeral_multiplier/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var s=a===r}else var s=!0;if(s){if(e.storage_priority!==void 0){let t=r;u(e.storage_priority,{instancePath:n+"/storage_priority",parentData:e,parentDataProperty:"storage_priority",rootData:m,dynamicAnchors:p})||(i=i===null?u.errors:i.concat(u.errors),r=i.length);var s=t===r}else var s=!0;if(s){if(e.ptz_enabled!==void 0){let t=r;if(typeof e.ptz_enabled!="boolean")return l.errors=[{instancePath:n+"/ptz_enabled",schemaPath:"#/properties/ptz_enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var s=t===r}else var s=!0;if(s){if(e.ptz_max_x!==void 0){let t=r;u(e.ptz_max_x,{instancePath:n+"/ptz_max_x",parentData:e,parentDataProperty:"ptz_max_x",rootData:m,dynamicAnchors:p})||(i=i===null?u.errors:i.concat(u.errors),r=i.length);var s=t===r}else var s=!0;if(s){if(e.ptz_max_y!==void 0){let t=r;u(e.ptz_max_y,{instancePath:n+"/ptz_max_y",parentData:e,parentDataProperty:"ptz_max_y",rootData:m,dynamicAnchors:p})||(i=i===null?u.errors:i.concat(u.errors),r=i.length);var s=t===r}else var s=!0;if(s){if(e.ptz_max_z!==void 0){let t=r;u(e.ptz_max_z,{instancePath:n+"/ptz_max_z",parentData:e,parentDataProperty:"ptz_max_z",rootData:m,dynamicAnchors:p})||(i=i===null?u.errors:i.concat(u.errors),r=i.length);var s=t===r}else var s=!0;if(s){if(e.ptz_has_home!==void 0){let t=r;if(typeof e.ptz_has_home!="boolean")return l.errors=[{instancePath:n+"/ptz_has_home",schemaPath:"#/properties/ptz_has_home/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var s=t===r}else var s=!0;if(s){if(e.record_on_schedule!==void 0){let t=r;if(typeof e.record_on_schedule!="boolean")return l.errors=[{instancePath:n+"/record_on_schedule",schemaPath:"#/properties/record_on_schedule/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var s=t===r}else var s=!0;if(s){if(e.recording_schedule!==void 0){let t=r;v(e.recording_schedule,{instancePath:n+"/recording_schedule",parentData:e,parentDataProperty:"recording_schedule",rootData:m,dynamicAnchors:p})||(i=i===null?v.errors:i.concat(v.errors),r=i.length);var s=t===r}else var s=!0;if(s){if(e.detection_record_on_schedule!==void 0){let t=r;if(typeof e.detection_record_on_schedule!="boolean")return l.errors=[{instancePath:n+"/detection_record_on_schedule",schemaPath:"#/properties/detection_record_on_schedule/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var s=t===r}else var s=!0;if(s){if(e.detection_recording_schedule!==void 0){let t=r;v(e.detection_recording_schedule,{instancePath:n+"/detection_recording_schedule",parentData:e,parentDataProperty:"detection_recording_schedule",rootData:m,dynamicAnchors:p})||(i=i===null?v.errors:i.concat(v.errors),r=i.length);var s=t===r}else var s=!0;if(s){if(e.tags!==void 0){let t=e.tags,a=r;if(d(t,{instancePath:n+"/tags",parentData:e,parentDataProperty:"tags",rootData:m,dynamicAnchors:p})||(i=i===null?d.errors:i.concat(d.errors),r=i.length),r===a&&typeof t=="string"&&h(t)>255)return l.errors=[{instancePath:n+"/tags",schemaPath:"#/properties/tags/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1;var s=a===r}else var s=!0;if(s){if(e.admin_url!==void 0){let t=e.admin_url,a=r;if(d(t,{instancePath:n+"/admin_url",parentData:e,parentDataProperty:"admin_url",rootData:m,dynamicAnchors:p})||(i=i===null?d.errors:i.concat(d.errors),r=i.length),r===a&&typeof t=="string"&&h(t)>511)return l.errors=[{instancePath:n+"/admin_url",schemaPath:"#/properties/admin_url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1;var s=a===r}else var s=!0;if(s){if(e.motion_trigger_source!==void 0){let t=e.motion_trigger_source,a=r;if(d(t,{instancePath:n+"/motion_trigger_source",parentData:e,parentDataProperty:"motion_trigger_source",rootData:m,dynamicAnchors:p})||(i=i===null?d.errors:i.concat(d.errors),r=i.length),r===a&&typeof t=="string"&&h(t)>255)return l.errors=[{instancePath:n+"/motion_trigger_source",schemaPath:"#/properties/motion_trigger_source/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1;var s=a===r}else var s=!0;if(s){if(e.go2rtc_source_override!==void 0){let t=e.go2rtc_source_override,a=r;if(d(t,{instancePath:n+"/go2rtc_source_override",parentData:e,parentDataProperty:"go2rtc_source_override",rootData:m,dynamicAnchors:p})||(i=i===null?d.errors:i.concat(d.errors),r=i.length),r===a&&typeof t=="string"&&h(t)>8191)return l.errors=[{instancePath:n+"/go2rtc_source_override",schemaPath:"#/properties/go2rtc_source_override/maxLength",keyword:"maxLength",params:{limit:8191},message:"must NOT have more than 8191 characters"}],!1;var s=a===r}else var s=!0;if(s){if(e.sub_stream_url!==void 0){let t=e.sub_stream_url,a=r;if(d(t,{instancePath:n+"/sub_stream_url",parentData:e,parentDataProperty:"sub_stream_url",rootData:m,dynamicAnchors:p})||(i=i===null?d.errors:i.concat(d.errors),r=i.length),r===a&&typeof t=="string"&&h(t)>511)return l.errors=[{instancePath:n+"/sub_stream_url",schemaPath:"#/properties/sub_stream_url/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1;var s=a===r}else var s=!0;if(s){if(e.detection_url!==void 0){let t=r;x(e.detection_url,{instancePath:n+"/detection_url",parentData:e,parentDataProperty:"detection_url",rootData:m,dynamicAnchors:p})||(i=i===null?x.errors:i.concat(x.errors),r=i.length);var s=t===r}else var s=!0;if(s)if(e.publish_url!==void 0){let t=r;P(e.publish_url,{instancePath:n+"/publish_url",parentData:e,parentDataProperty:"publish_url",rootData:m,dynamicAnchors:p})||(i=i===null?P.errors:i.concat(P.errors),r=i.length);var s=t===r}else var s=!0}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}else return l.errors=[{instancePath:n,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return l.errors=i,r===0}l.evaluated={props:!0,dynamicProps:!1,dynamicItems:!1};export{q as validateStreamUpdateRequest}; diff --git a/web/js/api/generated/validators/SummaryForbiddenSensitiveKeys.d.mts b/web/js/api/generated/validators/SummaryForbiddenSensitiveKeys.d.mts new file mode 100644 index 000000000..dd19ef92d --- /dev/null +++ b/web/js/api/generated/validators/SummaryForbiddenSensitiveKeys.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateSummaryForbiddenSensitiveKeys } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/SummaryForbiddenSensitiveKeys.mjs b/web/js/api/generated/validators/SummaryForbiddenSensitiveKeys.mjs new file mode 100644 index 000000000..9128803f1 --- /dev/null +++ b/web/js/api/generated/validators/SummaryForbiddenSensitiveKeys.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var S=u;function u(r,{instancePath:_="",parentData:j,parentDataProperty:q,rootData:w=r,dynamicAnchors:P={}}={}){let e=null,s=0,t=u.evaluated;t.dynamicProps&&(t.props=void 0),t.dynamicItems&&(t.items=void 0);let f=s,v=s,c=s,l=!1,y=s;if(r&&typeof r=="object"&&!Array.isArray(r)){let n;if(r.url===void 0&&(n="url")){let i={};e===null?e=[i]:e.push(i),s++}}var o=y===s;l=l||o;let d=s;if(r&&typeof r=="object"&&!Array.isArray(r)){let n;if(r.onvif_username===void 0&&(n="onvif_username")){let i={};e===null?e=[i]:e.push(i),s++}}var o=d===s;l=l||o;let p=s;if(r&&typeof r=="object"&&!Array.isArray(r)){let n;if(r.onvif_password===void 0&&(n="onvif_password")){let i={};e===null?e=[i]:e.push(i),s++}}var o=p===s;l=l||o;let m=s;if(r&&typeof r=="object"&&!Array.isArray(r)){let n;if(r.sub_stream_url===void 0&&(n="sub_stream_url")){let i={};e===null?e=[i]:e.push(i),s++}}var o=m===s;l=l||o;let a=s;if(r&&typeof r=="object"&&!Array.isArray(r)){let n;if(r.detection_url===void 0&&(n="detection_url")){let i={};e===null?e=[i]:e.push(i),s++}}var o=a===s;l=l||o;let h=s;if(r&&typeof r=="object"&&!Array.isArray(r)){let n;if(r.publish_url===void 0&&(n="publish_url")){let i={};e===null?e=[i]:e.push(i),s++}}var o=h===s;l=l||o;let b=s;if(r&&typeof r=="object"&&!Array.isArray(r)){let n;if(r.go2rtc_source_override===void 0&&(n="go2rtc_source_override")){let i={};e===null?e=[i]:e.push(i),s++}}var o=b===s;l=l||o;let A=s;if(r&&typeof r=="object"&&!Array.isArray(r)){let n;if(r.detection_api_url===void 0&&(n="detection_api_url")){let i={};e===null?e=[i]:e.push(i),s++}}var o=A===s;if(l=l||o,l)s=c,e!==null&&(c?e.length=c:e=null);else{let n={};e===null?e=[n]:e.push(n),s++}var g=v===s;return g?(u.errors=[{instancePath:_,schemaPath:"#/not",keyword:"not",params:{},message:"must NOT be valid"}],!1):(s=f,e!==null&&(f?e.length=f:e=null),u.errors=e,s===0)}u.evaluated={dynamicProps:!1,dynamicItems:!1};export{S as validateSummaryForbiddenSensitiveKeys}; diff --git a/web/js/api/generated/validators/SummaryPageMeta.d.mts b/web/js/api/generated/validators/SummaryPageMeta.d.mts new file mode 100644 index 000000000..48a21b64d --- /dev/null +++ b/web/js/api/generated/validators/SummaryPageMeta.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateSummaryPageMeta } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/SummaryPageMeta.mjs b/web/js/api/generated/validators/SummaryPageMeta.mjs new file mode 100644 index 000000000..7b32b2515 --- /dev/null +++ b/web/js/api/generated/validators/SummaryPageMeta.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var y=t;function t(r,{instancePath:s="",parentData:u,parentDataProperty:l,rootData:g=r,dynamicAnchors:f={}}={}){let n=null,i=0,o=t.evaluated;if(o.dynamicProps&&(o.props=void 0),o.dynamicItems&&(o.items=void 0),i===0)if(r&&typeof r=="object"&&!Array.isArray(r)){let p;if(r.total===void 0&&(p="total")||r.page===void 0&&(p="page")||r.page_size===void 0&&(p="page_size")||r.total_pages===void 0&&(p="total_pages"))return t.errors=[{instancePath:s,schemaPath:"#/required",keyword:"required",params:{missingProperty:p},message:"must have required property '"+p+"'"}],!1;if(r.total!==void 0){let e=r.total,a=i;if(!(typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)))return t.errors=[{instancePath:s+"/total",schemaPath:"#/properties/total/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;if(i===a&&typeof e=="number"&&isFinite(e)&&(e<0||isNaN(e)))return t.errors=[{instancePath:s+"/total",schemaPath:"#/properties/total/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1;var m=a===i}else var m=!0;if(m){if(r.page!==void 0){let e=r.page,a=i;if(!(typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)))return t.errors=[{instancePath:s+"/page",schemaPath:"#/properties/page/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;if(i===a&&typeof e=="number"&&isFinite(e)){if(e>1e6||isNaN(e))return t.errors=[{instancePath:s+"/page",schemaPath:"#/properties/page/maximum",keyword:"maximum",params:{comparison:"<=",limit:1e6},message:"must be <= 1000000"}],!1;if(e<1||isNaN(e))return t.errors=[{instancePath:s+"/page",schemaPath:"#/properties/page/minimum",keyword:"minimum",params:{comparison:">=",limit:1},message:"must be >= 1"}],!1}var m=a===i}else var m=!0;if(m){if(r.page_size!==void 0){let e=r.page_size,a=i;if(!(typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)))return t.errors=[{instancePath:s+"/page_size",schemaPath:"#/properties/page_size/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;if(i===a&&typeof e=="number"&&isFinite(e)){if(e>100||isNaN(e))return t.errors=[{instancePath:s+"/page_size",schemaPath:"#/properties/page_size/maximum",keyword:"maximum",params:{comparison:"<=",limit:100},message:"must be <= 100"}],!1;if(e<1||isNaN(e))return t.errors=[{instancePath:s+"/page_size",schemaPath:"#/properties/page_size/minimum",keyword:"minimum",params:{comparison:">=",limit:1},message:"must be >= 1"}],!1}var m=a===i}else var m=!0;if(m)if(r.total_pages!==void 0){let e=r.total_pages,a=i;if(!(typeof e=="number"&&!(e%1)&&!isNaN(e)&&isFinite(e)))return t.errors=[{instancePath:s+"/total_pages",schemaPath:"#/properties/total_pages/type",keyword:"type",params:{type:"integer"},message:"must be integer"}],!1;if(i===a&&typeof e=="number"&&isFinite(e)&&(e<0||isNaN(e)))return t.errors=[{instancePath:s+"/total_pages",schemaPath:"#/properties/total_pages/minimum",keyword:"minimum",params:{comparison:">=",limit:0},message:"must be >= 0"}],!1;var m=a===i}else var m=!0}}}else return t.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return t.errors=n,i===0}t.evaluated={props:{total:!0,page:!0,page_size:!0,total_pages:!0},dynamicProps:!1,dynamicItems:!1};export{y as validateSummaryPageMeta}; diff --git a/web/js/api/generated/validators/SummaryStatus.d.mts b/web/js/api/generated/validators/SummaryStatus.d.mts new file mode 100644 index 000000000..4ebbd91b0 --- /dev/null +++ b/web/js/api/generated/validators/SummaryStatus.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateSummaryStatus } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/SummaryStatus.mjs b/web/js/api/generated/validators/SummaryStatus.mjs new file mode 100644 index 000000000..64b00c8d2 --- /dev/null +++ b/web/js/api/generated/validators/SummaryStatus.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var d=r,o={type:"string",enum:["Running","Degraded","Error","Stopped","Unknown"],$id:"urn:lightnvr:contract:SummaryStatus"};function r(e,{instancePath:t="",parentData:u,parentDataProperty:m,rootData:i=e,dynamicAnchors:l={}}={}){let s=null,a=0,n=r.evaluated;return n.dynamicProps&&(n.props=void 0),n.dynamicItems&&(n.items=void 0),typeof e!="string"?(r.errors=[{instancePath:t,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1):e==="Running"||e==="Degraded"||e==="Error"||e==="Stopped"||e==="Unknown"?(r.errors=s,a===0):(r.errors=[{instancePath:t,schemaPath:"#/enum",keyword:"enum",params:{allowedValues:o.enum},message:"must be equal to one of the allowed values"}],!1)}r.evaluated={dynamicProps:!1,dynamicItems:!1};export{d as validateSummaryStatus}; diff --git a/web/js/api/generated/validators/UrlWithoutQuerySecrets.d.mts b/web/js/api/generated/validators/UrlWithoutQuerySecrets.d.mts new file mode 100644 index 000000000..dad6a29a3 --- /dev/null +++ b/web/js/api/generated/validators/UrlWithoutQuerySecrets.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateUrlWithoutQuerySecrets } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/UrlWithoutQuerySecrets.mjs b/web/js/api/generated/validators/UrlWithoutQuerySecrets.mjs new file mode 100644 index 000000000..bde68f4cf --- /dev/null +++ b/web/js/api/generated/validators/UrlWithoutQuerySecrets.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var m=r;var c=new RegExp("[?&;][^=&#]*([Pp][Aa][Ss][Ss]|[Pp][Ww][Dd]|[Tt][Oo][Kk][Ee][Nn]|[Ss][Ee][Cc][Rr][Ee][Tt]|[Aa][Pp][Ii]_?[Kk][Ee][Yy])[^=&#]*=[^&#]","u");function r(s,{instancePath:i="",parentData:u,parentDataProperty:d,rootData:f=s,dynamicAnchors:y={}}={}){let e=null,t=0,n=r.evaluated;if(n.dynamicProps&&(n.props=void 0),n.dynamicItems&&(n.items=void 0),typeof s!="string")return r.errors=[{instancePath:i,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;let a=t,l=t;if(typeof s=="string"&&!c.test(s)){let o={};e===null?e=[o]:e.push(o),t++}var p=l===t;return p?(r.errors=[{instancePath:i,schemaPath:"#/not",keyword:"not",params:{},message:"must NOT be valid"}],!1):(t=a,e!==null&&(a?e.length=a:e=null),r.errors=e,t===0)}r.evaluated={dynamicProps:!1,dynamicItems:!1};export{m as validateUrlWithoutQuerySecrets}; diff --git a/web/js/api/generated/validators/UrlWithoutUserinfo.d.mts b/web/js/api/generated/validators/UrlWithoutUserinfo.d.mts new file mode 100644 index 000000000..287b6213b --- /dev/null +++ b/web/js/api/generated/validators/UrlWithoutUserinfo.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateUrlWithoutUserinfo } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/UrlWithoutUserinfo.mjs b/web/js/api/generated/validators/UrlWithoutUserinfo.mjs new file mode 100644 index 000000000..a2ad4eb92 --- /dev/null +++ b/web/js/api/generated/validators/UrlWithoutUserinfo.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var m=(t,e)=>()=>{try{return e||t((e={exports:{}}).exports,e),e.exports}catch(n){throw e=0,n}};var c=m(o=>{"use strict";Object.defineProperty(o,"__esModule",{value:!0});function u(t){let e=t.length,n=0,a=0,s;for(;a=55296&&s<=56319&&a511)return r.errors=[{instancePath:e,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1;if(!p.test(t))return r.errors=[{instancePath:e,schemaPath:"#/pattern",keyword:"pattern",params:{pattern:"^(?![A-Za-z][A-Za-z0-9+.-]*://[^/?#]*@)"},message:'must match pattern "^(?![A-Za-z][A-Za-z0-9+.-]*://[^/?#]*@)"'}],!1}else return r.errors=[{instancePath:e,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return r.errors=f,l===0}r.evaluated={dynamicProps:!1,dynamicItems:!1};export{v as validateUrlWithoutUserinfo}; diff --git a/web/js/api/generated/validators/ViewerSafeUrl.d.mts b/web/js/api/generated/validators/ViewerSafeUrl.d.mts new file mode 100644 index 000000000..3baf626a0 --- /dev/null +++ b/web/js/api/generated/validators/ViewerSafeUrl.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateViewerSafeUrl } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/ViewerSafeUrl.mjs b/web/js/api/generated/validators/ViewerSafeUrl.mjs new file mode 100644 index 000000000..8739651c5 --- /dev/null +++ b/web/js/api/generated/validators/ViewerSafeUrl.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var A=(e,t)=>()=>{try{return t||e((t={exports:{}}).exports,t),t.exports}catch(l){throw t=0,l}};var g=A(h=>{"use strict";Object.defineProperty(h,"__esModule",{value:!0});function v(e){let t=e.length,l=0,a=0,i;for(;a=55296&&i<=56319&&a511)return o.errors=[{instancePath:t,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:511},message:"must NOT have more than 511 characters"}],!1;if(!E.test(e))return o.errors=[{instancePath:t,schemaPath:"#/pattern",keyword:"pattern",params:{pattern:"^(?![A-Za-z][A-Za-z0-9+.-]*://[^/?#]*@)"},message:'must match pattern "^(?![A-Za-z][A-Za-z0-9+.-]*://[^/?#]*@)"'}],!1}else return o.errors=[{instancePath:t,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return o.errors=r,s===0}o.evaluated={dynamicProps:!1,dynamicItems:!1};var w=new RegExp("[?&;][^=&#]*([Pp][Aa][Ss][Ss]|[Pp][Ww][Dd]|[Tt][Oo][Kk][Ee][Nn]|[Ss][Ee][Cc][Rr][Ee][Tt]|[Aa][Pp][Ii]_?[Kk][Ee][Yy])[^=&#]*=[^&#]","u");function c(e,{instancePath:t="",parentData:l,parentDataProperty:a,rootData:i=e,dynamicAnchors:f={}}={}){let r=null,s=0,n=c.evaluated;if(n.dynamicProps&&(n.props=void 0),n.dynamicItems&&(n.items=void 0),typeof e!="string")return c.errors=[{instancePath:t,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;let u=s,d=s;if(typeof e=="string"&&!w.test(e)){let y={};r===null?r=[y]:r.push(y),s++}var p=d===s;return p?(c.errors=[{instancePath:t,schemaPath:"#/not",keyword:"not",params:{},message:"must NOT be valid"}],!1):(s=u,r!==null&&(u?r.length=u:r=null),c.errors=r,s===0)}c.evaluated={dynamicProps:!1,dynamicItems:!1};function m(e,{instancePath:t="",parentData:l,parentDataProperty:a,rootData:i=e,dynamicAnchors:f={}}={}){let r=null,s=0,n=m.evaluated;n.dynamicProps&&(n.props=void 0),n.dynamicItems&&(n.items=void 0);let u=s;o(e,{instancePath:t,parentData:l,parentDataProperty:a,rootData:i,dynamicAnchors:f})||(r=r===null?o.errors:r.concat(o.errors),s=r.length);var d=u===s;if(d){let p=s;c(e,{instancePath:t,parentData:l,parentDataProperty:a,rootData:i,dynamicAnchors:f})||(r=r===null?c.errors:r.concat(c.errors),s=r.length);var d=p===s}return m.errors=r,s===0}m.evaluated={dynamicProps:!1,dynamicItems:!1};export{k as validateViewerSafeUrl}; diff --git a/web/js/api/generated/validators/ZonePoint.d.mts b/web/js/api/generated/validators/ZonePoint.d.mts new file mode 100644 index 000000000..297cb337e --- /dev/null +++ b/web/js/api/generated/validators/ZonePoint.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateZonePoint } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/ZonePoint.mjs b/web/js/api/generated/validators/ZonePoint.mjs new file mode 100644 index 000000000..ac917804a --- /dev/null +++ b/web/js/api/generated/validators/ZonePoint.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var l=r;function r(e,{instancePath:i="",parentData:y,parentDataProperty:m,rootData:d=e,dynamicAnchors:f={}}={}){let u=null,t=0,n=r.evaluated;if(n.dynamicProps&&(n.props=void 0),n.dynamicItems&&(n.items=void 0),t===0)if(e&&typeof e=="object"&&!Array.isArray(e)){let o;if(e.x===void 0&&(o="x")||e.y===void 0&&(o="y"))return r.errors=[{instancePath:i,schemaPath:"#/required",keyword:"required",params:{missingProperty:o},message:"must have required property '"+o+"'"}],!1;if(e.x!==void 0){let s=e.x,p=t;if(!(typeof s=="number"&&isFinite(s)))return r.errors=[{instancePath:i+"/x",schemaPath:"#/properties/x/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var a=p===t}else var a=!0;if(a)if(e.y!==void 0){let s=e.y,p=t;if(!(typeof s=="number"&&isFinite(s)))return r.errors=[{instancePath:i+"/y",schemaPath:"#/properties/y/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var a=p===t}else var a=!0}else return r.errors=[{instancePath:i,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return r.errors=u,t===0}r.evaluated={props:{x:!0,y:!0},dynamicProps:!1,dynamicItems:!1};export{l as validateZonePoint}; diff --git a/web/js/api/generated/validators/ZonesResponse.d.mts b/web/js/api/generated/validators/ZonesResponse.d.mts new file mode 100644 index 000000000..4eb7678a0 --- /dev/null +++ b/web/js/api/generated/validators/ZonesResponse.d.mts @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +export { validateZonesResponse } from '../validators.mjs'; diff --git a/web/js/api/generated/validators/ZonesResponse.mjs b/web/js/api/generated/validators/ZonesResponse.mjs new file mode 100644 index 000000000..d7a7fc8a4 --- /dev/null +++ b/web/js/api/generated/validators/ZonesResponse.mjs @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Generated by scripts/quality/contracts.mjs; do not edit. +var D=(e,s)=>()=>{try{return s||e((s={exports:{}}).exports,s),s.exports}catch(d){throw s=0,d}};var k=D(P=>{"use strict";Object.defineProperty(P,"__esModule",{value:!0});function w(e){let s=e.length,d=0,y=0,p;for(;y=55296&&p<=56319&&y255)return u.errors=[{instancePath:s,schemaPath:"#/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1;if(v(e)<1)return u.errors=[{instancePath:s,schemaPath:"#/minLength",keyword:"minLength",params:{limit:1},message:"must NOT have fewer than 1 characters"}],!1;if(new TextEncoder().encode(e).byteLength>255)return u.errors=[{instancePath:s,schemaPath:"#/x-lightnvr-max-bytes",keyword:"x-lightnvr-max-bytes",params:{},message:"must fit the UTF-8 byte limit"}],!1}else return u.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;return u.errors=o,r===0}u.evaluated={dynamicProps:!1,dynamicItems:!1};function c(e,{instancePath:s="",parentData:d,parentDataProperty:y,rootData:p=e,dynamicAnchors:g={}}={}){let o=null,r=0,m=c.evaluated;if(m.dynamicProps&&(m.props=void 0),m.dynamicItems&&(m.items=void 0),r===0)if(e&&typeof e=="object"&&!Array.isArray(e)){let f;if(e.x===void 0&&(f="x")||e.y===void 0&&(f="y"))return c.errors=[{instancePath:s,schemaPath:"#/required",keyword:"required",params:{missingProperty:f},message:"must have required property '"+f+"'"}],!1;if(e.x!==void 0){let n=e.x,a=r;if(!(typeof n=="number"&&isFinite(n)))return c.errors=[{instancePath:s+"/x",schemaPath:"#/properties/x/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var t=a===r}else var t=!0;if(t)if(e.y!==void 0){let n=e.y,a=r;if(!(typeof n=="number"&&isFinite(n)))return c.errors=[{instancePath:s+"/y",schemaPath:"#/properties/y/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var t=a===r}else var t=!0}else return c.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return c.errors=o,r===0}c.evaluated={props:{x:!0,y:!0},dynamicProps:!1,dynamicItems:!1};function i(e,{instancePath:s="",parentData:d,parentDataProperty:y,rootData:p=e,dynamicAnchors:g={}}={}){let o=null,r=0,m=i.evaluated;if(m.dynamicProps&&(m.props=void 0),m.dynamicItems&&(m.items=void 0),r===0)if(e&&typeof e=="object"&&!Array.isArray(e)){let n;if(e.id===void 0&&(n="id")||e.stream_name===void 0&&(n="stream_name")||e.name===void 0&&(n="name")||e.enabled===void 0&&(n="enabled")||e.color===void 0&&(n="color")||e.polygon===void 0&&(n="polygon")||e.filter_classes===void 0&&(n="filter_classes")||e.min_confidence===void 0&&(n="min_confidence"))return i.errors=[{instancePath:s,schemaPath:"#/required",keyword:"required",params:{missingProperty:n},message:"must have required property '"+n+"'"}],!1;if(e.id!==void 0){let a=e.id,l=r;if(r===l)if(typeof a=="string"){if(v(a)>63)return i.errors=[{instancePath:s+"/id",schemaPath:"#/properties/id/maxLength",keyword:"maxLength",params:{limit:63},message:"must NOT have more than 63 characters"}],!1}else return i.errors=[{instancePath:s+"/id",schemaPath:"#/properties/id/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var t=l===r}else var t=!0;if(t){if(e.stream_name!==void 0){let a=r;u(e.stream_name,{instancePath:s+"/stream_name",parentData:e,parentDataProperty:"stream_name",rootData:p,dynamicAnchors:g})||(o=o===null?u.errors:o.concat(u.errors),r=o.length);var t=a===r}else var t=!0;if(t){if(e.name!==void 0){let a=e.name,l=r;if(r===l)if(typeof a=="string"){if(v(a)>63)return i.errors=[{instancePath:s+"/name",schemaPath:"#/properties/name/maxLength",keyword:"maxLength",params:{limit:63},message:"must NOT have more than 63 characters"}],!1}else return i.errors=[{instancePath:s+"/name",schemaPath:"#/properties/name/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var t=l===r}else var t=!0;if(t){if(e.enabled!==void 0){let a=r;if(typeof e.enabled!="boolean")return i.errors=[{instancePath:s+"/enabled",schemaPath:"#/properties/enabled/type",keyword:"type",params:{type:"boolean"},message:"must be boolean"}],!1;var t=a===r}else var t=!0;if(t){if(e.color!==void 0){let a=e.color,l=r;if(r===l)if(typeof a=="string"){if(v(a)>7)return i.errors=[{instancePath:s+"/color",schemaPath:"#/properties/color/maxLength",keyword:"maxLength",params:{limit:7},message:"must NOT have more than 7 characters"}],!1}else return i.errors=[{instancePath:s+"/color",schemaPath:"#/properties/color/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var t=l===r}else var t=!0;if(t){if(e.polygon!==void 0){let a=e.polygon,l=r;if(r===l)if(Array.isArray(a)){if(a.length>32)return i.errors=[{instancePath:s+"/polygon",schemaPath:"#/properties/polygon/maxItems",keyword:"maxItems",params:{limit:32},message:"must NOT have more than 32 items"}],!1;{var f=!0;let x=a.length;for(let _=0;_255)return i.errors=[{instancePath:s+"/filter_classes",schemaPath:"#/properties/filter_classes/maxLength",keyword:"maxLength",params:{limit:255},message:"must NOT have more than 255 characters"}],!1}else return i.errors=[{instancePath:s+"/filter_classes",schemaPath:"#/properties/filter_classes/type",keyword:"type",params:{type:"string"},message:"must be string"}],!1;var t=l===r}else var t=!0;if(t)if(e.min_confidence!==void 0){let a=e.min_confidence,l=r;if(!(typeof a=="number"&&isFinite(a)))return i.errors=[{instancePath:s+"/min_confidence",schemaPath:"#/properties/min_confidence/type",keyword:"type",params:{type:"number"},message:"must be number"}],!1;var t=l===r}else var t=!0}}}}}}}else return i.errors=[{instancePath:s,schemaPath:"#/type",keyword:"type",params:{type:"object"},message:"must be object"}],!1;return i.errors=o,r===0}i.evaluated={props:{id:!0,stream_name:!0,name:!0,enabled:!0,color:!0,polygon:!0,filter_classes:!0,min_confidence:!0},dynamicProps:!1,dynamicItems:!1};function h(e,{instancePath:s="",parentData:d,parentDataProperty:y,rootData:p=e,dynamicAnchors:g={}}={}){let o=null,r=0,m=h.evaluated;if(m.dynamicProps&&(m.props=void 0),m.dynamicItems&&(m.items=void 0),r===0)if(e&&typeof e=="object"&&!Array.isArray(e)){let f;if(e.zones===void 0&&(f="zones"))return h.errors=[{instancePath:s,schemaPath:"#/required",keyword:"required",params:{missingProperty:f},message:"must have required property '"+f+"'"}],!1;if(e.zones!==void 0){let n=e.zones;if(r===r)if(Array.isArray(n)){if(n.length>16)return h.errors=[{instancePath:s+"/zones",schemaPath:"#/properties/zones/maxItems",keyword:"maxItems",params:{limit:16},message:"must NOT have more than 16 items"}],!1;{var t=!0;let l=n.length;for(let b=0;b