mirror of
https://github.com/gadgethd/ukmesh.git
synced 2026-10-23 22:32:52 +00:00
fix(ci): pin gitleaks-action to valid commit after upstream force-push (#63)
* fix(ci): pin gitleaks-action to valid commit after upstream force-push Upstream deleted dcedce43 (force-push), so every ci.yml run fails at workflow-parse with 0 jobs. The dependabot actions-group bump (#30) contains this fix but cannot merge whole: its docker/* actions require node24, which GitHub runners do not support yet. Pin gitleaks alone (ff98106e is node20). * fix(ci): downgrade v7 actions to node20-compatible versions actions/checkout@v7, setup-node@v7, setup-python@v7 require node24, which GitHub-hosted runners do not support yet — every ci.yml run has failed at workflow-parse (0 jobs) since the Aug 3 v7 bump. Pin to the latest node20 versions (checkout v4, setup-node v4, setup-python v5). Also pins gitleaks-action to a valid commit (upstream force-pushed away dcedce43). * ci: noop retrigger * fix(ci): escape literal ${{ in bash string so GitHub parser accepts workflow The 'Validate tracked build and Compose inventory' step matched literal ${{ inside run-block strings, which GitHub's expression parser reads as the start of an expression (${{'* is invalid) — the workflow fails at parse time with 0 jobs. Split the literal as '$''{{' (bash concatenates adjacent quoted strings at runtime; the source no longer contains a contiguous ${{). * fix(ci): resolve all three pre-existing ukmesh CI failures Backend (packetBatchWriteCoalesce): - 045: restore nodes_public_visibility_generation trigger — 015 is superseded by 044 on fresh DBs so the trigger was never created, leaving generation/visibility_generation out of sync and public packet reads returning empty. - 046: restore packet privacy classification — the 042 fence design assumes sync_private_node_prefixes rewrites packets on privacy change, but 026's version only maintains prefixes. Node flips to private left old packets visible; direct SQL inserts were never classified. Restore the packet rewrite in the sync trigger and add a BEFORE INSERT trigger mirroring the batch path's is_private / visibility_ok computation. - packetBatch.integration.test.ts: exclude the prefix-cache refresh query from statementCount (matches unit-test convention). Frontend e2e: - public.spec.ts: assert the TopologyMap component's actual labels ('Geographic repeater topology map', '2 mapped repeaters · 2 observed relationships') instead of the pre-map SVG graph labels. Workers (pip install): - viewshed-worker: bump numpy 1.26.4→2.3.5, scipy 1.13.1→1.16.3, psycopg2-binary 2.9.10→2.9.11 (cp314 wheels); the base gdal image ships Python 3.14 so the old pins had no wheels. Inherit shapely from the image's python3-shapely apt package (no cp314 wheel exists; source builds are GEOS 3.14-incompatible). * fix(ci): restore data-plane services for compose validation and smoke test Commit8c5e1c8split the long-lived data-plane services (timescaledb, mosquitto, redis, mosquitto-reloader) into the external meshcore-infra project on the live host, but the Workers-and-Compose CI job still asserted their presence in the app compose project (max_worker_processes check, inventory check, smoke-test execs). CI had been broken since Aug 3 so the mismatch was never caught. Add docker-compose.ci.yml — a CI-only overlay restoring the four data-plane services from their pre-split definitions — and point every compose invocation in the Workers-and-Compose job at '-f docker-compose.yml -f docker-compose.ci.yml'. Validated locally: merged config parses, the jq assertions and the full inventory (db-migrate backend app-ukmesh website-ukmesh website-dev mesh-health-check mosquitto-reloader link-worker link-backfill-worker hopreach timescaledb mosquitto) all pass. * fix(ci): include dev profile in compose inventory check website-dev is a dev-profile service; the inventory gate compared the base-config service list, so the merged stack never matched. This gate has been red since the workflow was first written (CI parse-broken from Aug 3 until the gitleaks pin landed). * fix(ci): db-migrate must wait for timescaledb health The Aug 9 data-plane split removed db-migrate's depends_on along with the timescaledb service definition, so the migration runner raced the fresh database init in the empty-volume smoke test and failed on the not-yet-created base schema. Restore the pre-split condition: timescaledb service_healthy. * fix(ci): restore internal TCP MQTT listener for smoke test + exporter Commit40e843fdropped the 1883 TCP listener, breaking the CI smoke (mosquitto_pub targets 1883) and the Prometheus mosquitto exporter. The listener is internal-only: compose never publishes 1883 on the host, and the password/ACL policy still applies. --------- Co-authored-by: gadgethd <111318106+gadgethd@users.noreply.github.com>
This commit is contained in:
1 parent
a0addabeae
commit
ab6372b8a9
11 files changed
+385
-213
No files matched your search
+24
-24
@@ -45,8 +45,8 @@ jobs:
|
||||
run:
|
||||
working-directory: backend
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v7
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v7
|
||||
with:
|
||||
node-version: 20
|
||||
cache: npm
|
||||
@@ -68,8 +68,8 @@ jobs:
|
||||
run:
|
||||
working-directory: frontend
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v7
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v7
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
@@ -98,13 +98,13 @@ jobs:
|
||||
SOURCE_REVISION: ${{ github.sha }}
|
||||
MOSQUITTO_IMAGE: eclipse-mosquitto@sha256:9cfdd46ad59f3e3e5f592f6baf57ab23e1ad00605509d0f5c1e9b179c5314d87
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v7
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v7
|
||||
with:
|
||||
node-version: 22
|
||||
- name: Validate release security waivers
|
||||
run: node scripts/check-trivy-waivers.mjs
|
||||
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v7
|
||||
with:
|
||||
python-version: '3.12'
|
||||
- name: Test operator scripts
|
||||
@@ -181,8 +181,8 @@ jobs:
|
||||
done
|
||||
- name: Validate Compose configuration
|
||||
run: |
|
||||
docker compose config --quiet
|
||||
docker compose config --format json \
|
||||
docker compose -f docker-compose.yml -f docker-compose.ci.yml config --quiet
|
||||
docker compose -f docker-compose.yml -f docker-compose.ci.yml config --format json \
|
||||
| jq -e '
|
||||
(.services.timescaledb.command
|
||||
| index("max_worker_processes=24") != null)
|
||||
@@ -191,14 +191,14 @@ jobs:
|
||||
- name: Validate tracked build and Compose inventory
|
||||
run: |
|
||||
while IFS= read -r context; do
|
||||
[[ "$context" == *'${{'* ]] && continue
|
||||
[[ "$context" == *'$''{{'* ]] && continue
|
||||
test -d "$context"
|
||||
done < <(sed -n 's/^[[:space:]]*context: //p' .github/workflows/release.yml)
|
||||
while IFS= read -r dockerfile; do
|
||||
[[ "$dockerfile" == *'${{'* ]] && continue
|
||||
[[ "$dockerfile" == *'$''{{'* ]] && continue
|
||||
test -f "$dockerfile"
|
||||
done < <(sed -n 's/^[[:space:]]*file: //p' .github/workflows/release.yml)
|
||||
compose_services="$(docker compose config --services)"
|
||||
compose_services="$(docker compose -f docker-compose.yml -f docker-compose.ci.yml --profile dev config --services)"
|
||||
for service in \
|
||||
db-migrate backend app-ukmesh website-ukmesh website-dev mesh-health-check \
|
||||
mosquitto-reloader link-worker link-backfill-worker hopreach \
|
||||
@@ -212,7 +212,7 @@ jobs:
|
||||
publisher_password="ci-e2e-publisher-password"
|
||||
observer="CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"
|
||||
cleanup() {
|
||||
docker compose \
|
||||
docker compose -f docker-compose.yml -f docker-compose.ci.yml \
|
||||
--project-name "$project" \
|
||||
--profile dev \
|
||||
down --volumes --remove-orphans
|
||||
@@ -246,13 +246,13 @@ jobs:
|
||||
--build-arg SOURCE_REVISION="$SOURCE_REVISION" \
|
||||
--build-arg VITE_APP_HOSTNAME=app.ukmesh.com \
|
||||
-f Dockerfile -t meshcore-combined:test .
|
||||
docker compose \
|
||||
docker compose -f docker-compose.yml -f docker-compose.ci.yml \
|
||||
--project-name "$project" \
|
||||
--profile dev \
|
||||
build \
|
||||
backend app-ukmesh website-ukmesh website-dev mesh-health-check \
|
||||
mosquitto-reloader link-worker hopreach
|
||||
docker compose \
|
||||
docker compose -f docker-compose.yml -f docker-compose.ci.yml \
|
||||
--project-name "$project" \
|
||||
--profile dev \
|
||||
up --detach --no-build --scale link-backfill-worker=0 \
|
||||
@@ -260,7 +260,7 @@ jobs:
|
||||
# link-backfill-worker is intentionally a finite job. Running it
|
||||
# separately lets Compose wait for the long-lived stack without
|
||||
# treating this worker's successful exit as a health failure.
|
||||
docker compose \
|
||||
docker compose -f docker-compose.yml -f docker-compose.ci.yml \
|
||||
--project-name "$project" \
|
||||
--profile dev \
|
||||
run --rm --no-deps link-backfill-worker
|
||||
@@ -272,7 +272,7 @@ jobs:
|
||||
| jq -er '.id'
|
||||
)"
|
||||
sleep 1
|
||||
docker compose --project-name "$project" exec -T \
|
||||
docker compose -f docker-compose.yml -f docker-compose.ci.yml --project-name "$project" exec -T \
|
||||
-e HEALTH_SESSION_ID="$health_session_id" \
|
||||
mesh-health-check node --input-type=module <<'NODE'
|
||||
import { readFileSync } from 'node:fs';
|
||||
@@ -297,12 +297,12 @@ jobs:
|
||||
done
|
||||
done
|
||||
backend_metrics="$(
|
||||
docker compose --project-name "$project" exec -T backend \
|
||||
docker compose -f docker-compose.yml -f docker-compose.ci.yml --project-name "$project" exec -T backend \
|
||||
wget -qO- http://127.0.0.1:9091/metrics
|
||||
)"
|
||||
grep -q '^meshcore_process_' <<<"$backend_metrics"
|
||||
ws_log="$(mktemp)"
|
||||
docker compose --project-name "$project" exec -T \
|
||||
docker compose -f docker-compose.yml -f docker-compose.ci.yml --project-name "$project" exec -T \
|
||||
-e MQTT_E2E_OBSERVER="$observer" \
|
||||
backend node --input-type=module >"$ws_log" <<'NODE' &
|
||||
import WebSocket from 'ws';
|
||||
@@ -333,12 +333,12 @@ jobs:
|
||||
NODE
|
||||
ws_pid=$!
|
||||
sleep 2
|
||||
docker compose --project-name "$project" exec -T mosquitto \
|
||||
docker compose -f docker-compose.yml -f docker-compose.ci.yml --project-name "$project" exec -T mosquitto \
|
||||
mosquitto_pub -h 127.0.0.1 -p 1883 \
|
||||
-u "$publisher_user" -P "$publisher_password" \
|
||||
-t "meshcore-test/CI/${observer}/status" \
|
||||
-m '{"origin":"CI E2E observer","origin_id":"CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC","stats":{"battery_mv":4100,"uptime_secs":60}}'
|
||||
docker compose --project-name "$project" exec -T mosquitto \
|
||||
docker compose -f docker-compose.yml -f docker-compose.ci.yml --project-name "$project" exec -T mosquitto \
|
||||
mosquitto_pub -h 127.0.0.1 -p 1883 \
|
||||
-u "$publisher_user" -P "$publisher_password" \
|
||||
-t "meshcore-test/CI/${observer}/packets" \
|
||||
@@ -346,7 +346,7 @@ jobs:
|
||||
readback=""
|
||||
for _ in $(seq 1 30); do
|
||||
readback="$(
|
||||
docker compose --project-name "$project" exec -T timescaledb \
|
||||
docker compose -f docker-compose.yml -f docker-compose.ci.yml --project-name "$project" exec -T timescaledb \
|
||||
psql -U meshcore -d meshcore -Atc \
|
||||
"SELECT EXISTS(SELECT 1 FROM nodes WHERE node_id = '${observer}' AND network = 'test'), EXISTS(SELECT 1 FROM packets WHERE rx_node_id = '${observer}' AND network = 'test'), EXISTS(SELECT 1 FROM packet_hourly_stats WHERE network = 'test')"
|
||||
)"
|
||||
@@ -392,10 +392,10 @@ jobs:
|
||||
name: Secret scan
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Scan repository with gitleaks
|
||||
uses: gitleaks/gitleaks-action@dcedce43c6f43de0b836d1fe38946645c9c638dc # v2
|
||||
uses: gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196329070c7 # v2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -0,0 +1,20 @@
|
||||
SET LOCAL lock_timeout = '5s';
|
||||
SET LOCAL statement_timeout = '30s';
|
||||
|
||||
-- On fresh databases, migration 015 (which created the nodes generation
|
||||
-- trigger) is superseded by 044 and never runs, so the trigger is missing.
|
||||
-- Without it, a node-privacy transition bumps the public generation through
|
||||
-- bump_visibility_for_identity_table() (which deliberately leaves the
|
||||
-- materialization generation stale — fail closed) and nothing re-fences it,
|
||||
-- so every public packet read returns empty forever.
|
||||
--
|
||||
-- Recreate the trigger idempotently. It executes the current
|
||||
-- bump_public_visibility_generation() (042's version bumps BOTH the public
|
||||
-- generation and the materialization fence atomically), which is exactly the
|
||||
-- fresh-database contract 042's fence assumed. Existing databases that ran
|
||||
-- 015 already have this trigger and are unaffected.
|
||||
DROP TRIGGER IF EXISTS nodes_public_visibility_generation ON nodes;
|
||||
CREATE TRIGGER nodes_public_visibility_generation
|
||||
AFTER INSERT OR UPDATE OR DELETE ON nodes
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION bump_public_visibility_generation();
|
||||
@@ -0,0 +1,184 @@
|
||||
SET LOCAL lock_timeout = '5s';
|
||||
SET LOCAL statement_timeout = '30s';
|
||||
|
||||
-- The 042 fence design assumes sync_private_node_prefixes() rewrites packets
|
||||
-- whenever a node's privacy changes ("nodes_private_prefix_materialization
|
||||
-- sorts before this trigger and has already updated every affected packet").
|
||||
-- Migration 026's rewrite of that function dropped the packet UPDATE that 016
|
||||
-- had, and no later migration restored it. Combined with 6b0aa34 removing the
|
||||
-- query-time prefix probe, a node that flips private (name gains 🚫) leaves
|
||||
-- every packet already stored for it publicly visible, and packets inserted
|
||||
-- outside packetBatch (backfills, legacy rows, direct SQL) are never
|
||||
-- classified at all.
|
||||
--
|
||||
-- This migration restores the DB-level guarantee:
|
||||
-- 1. sync_private_node_prefixes() again rematerializes stored packets when
|
||||
-- a node's privacy/network changes (the 042 contract).
|
||||
-- 2. A BEFORE INSERT trigger classifies every packet against the current
|
||||
-- private_node_prefixes index, so no insert path can bypass privacy.
|
||||
CREATE OR REPLACE FUNCTION sync_private_node_prefixes()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
changed_node_id TEXT := COALESCE(NEW.node_id, OLD.node_id);
|
||||
changed_network TEXT := COALESCE(NEW.network, OLD.network, 'ukmesh');
|
||||
BEGIN
|
||||
IF TG_OP = 'UPDATE'
|
||||
AND OLD.name IS NOT DISTINCT FROM NEW.name
|
||||
AND OLD.network IS NOT DISTINCT FROM NEW.network THEN
|
||||
RETURN NEW;
|
||||
END IF;
|
||||
|
||||
DELETE FROM private_node_prefixes WHERE node_id = changed_node_id;
|
||||
|
||||
IF TG_OP <> 'DELETE' AND COALESCE(NEW.name, '') LIKE '%🚫%' THEN
|
||||
INSERT INTO private_node_prefixes (node_id, network, prefix_size_bytes, prefix)
|
||||
VALUES
|
||||
(NEW.node_id, COALESCE(NEW.network, 'ukmesh'), 1, UPPER(LEFT(NEW.node_id, 2))),
|
||||
(NEW.node_id, COALESCE(NEW.network, 'ukmesh'), 2, UPPER(LEFT(NEW.node_id, 4))),
|
||||
(NEW.node_id, COALESCE(NEW.network, 'ukmesh'), 3, UPPER(LEFT(NEW.node_id, 6)))
|
||||
ON CONFLICT (node_id, network, prefix_size_bytes) DO UPDATE SET
|
||||
prefix = EXCLUDED.prefix,
|
||||
updated_at = NOW();
|
||||
END IF;
|
||||
|
||||
-- A privacy-name or network change must also rematerialize packets already
|
||||
-- stored for this participant. Restrict the rewrite to rows that can mention
|
||||
-- the changed identity, then recompute against the complete prefix index.
|
||||
UPDATE packets p
|
||||
SET is_private = EXISTS (
|
||||
SELECT 1
|
||||
FROM private_node_prefixes pp
|
||||
WHERE (
|
||||
pp.network = p.network
|
||||
OR (
|
||||
pp.network IN ('ukmesh', 'northeast', 'teesside')
|
||||
AND p.network IN ('ukmesh', 'northeast', 'teesside')
|
||||
)
|
||||
)
|
||||
AND (
|
||||
pp.node_id IN (p.rx_node_id, p.src_node_id)
|
||||
OR (
|
||||
p.path_hash_size_bytes = pp.prefix_size_bytes
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM unnest(COALESCE(p.path_hashes, ARRAY[]::text[])) AS packet_prefix
|
||||
WHERE UPPER(packet_prefix) = pp.prefix
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
visibility_ok = (
|
||||
(COALESCE(cardinality(p.path_hashes), 0) = 0 OR p.path_hash_size_bytes BETWEEN 1 AND 3)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM private_node_prefixes pp
|
||||
WHERE (
|
||||
pp.network = p.network
|
||||
OR (
|
||||
pp.network IN ('ukmesh', 'northeast', 'teesside')
|
||||
AND p.network IN ('ukmesh', 'northeast', 'teesside')
|
||||
)
|
||||
)
|
||||
AND (
|
||||
pp.node_id IN (p.rx_node_id, p.src_node_id)
|
||||
OR (
|
||||
p.path_hash_size_bytes = pp.prefix_size_bytes
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM unnest(COALESCE(p.path_hashes, ARRAY[]::text[])) AS packet_prefix
|
||||
WHERE UPPER(packet_prefix) = pp.prefix
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
WHERE (
|
||||
p.rx_node_id = changed_node_id
|
||||
OR p.src_node_id = changed_node_id
|
||||
OR (
|
||||
p.network = changed_network
|
||||
OR (
|
||||
p.network IN ('ukmesh', 'northeast', 'teesside')
|
||||
AND changed_network IN ('ukmesh', 'northeast', 'teesside')
|
||||
)
|
||||
)
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM unnest(COALESCE(p.path_hashes, ARRAY[]::text[])) AS packet_prefix
|
||||
WHERE UPPER(packet_prefix) IN (
|
||||
UPPER(LEFT(changed_node_id, 2)),
|
||||
UPPER(LEFT(changed_node_id, 4)),
|
||||
UPPER(LEFT(changed_node_id, 6))
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
IF TG_OP = 'DELETE' THEN RETURN OLD; END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
DROP TRIGGER IF EXISTS nodes_private_prefix_materialization ON nodes;
|
||||
CREATE TRIGGER nodes_private_prefix_materialization
|
||||
AFTER INSERT OR UPDATE OR DELETE ON nodes
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION sync_private_node_prefixes();
|
||||
|
||||
-- Classify every packet at insert time against the current prefix index. This
|
||||
-- mirrors the packetBatch TS-side privatePrefixCache classification so that no
|
||||
-- insert path (direct SQL, backfills, legacy rows) can bypass privacy. The
|
||||
-- function is deliberately simple and index-bound: private_node_prefixes is
|
||||
-- tiny (only 🚫-named nodes) and the lookup key is the packets PK columns.
|
||||
CREATE OR REPLACE FUNCTION classify_packet_privacy()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
matches_private BOOLEAN;
|
||||
BEGIN
|
||||
SELECT EXISTS (
|
||||
SELECT 1
|
||||
FROM private_node_prefixes pp
|
||||
WHERE (
|
||||
pp.network = NEW.network
|
||||
OR (
|
||||
pp.network IN ('ukmesh', 'northeast', 'teesside')
|
||||
AND NEW.network IN ('ukmesh', 'northeast', 'teesside')
|
||||
)
|
||||
)
|
||||
AND (
|
||||
pp.node_id IN (NEW.rx_node_id, NEW.src_node_id)
|
||||
OR (
|
||||
NEW.path_hash_size_bytes = pp.prefix_size_bytes
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM unnest(COALESCE(NEW.path_hashes, ARRAY[]::text[])) AS packet_prefix
|
||||
WHERE UPPER(packet_prefix) = pp.prefix
|
||||
)
|
||||
)
|
||||
)
|
||||
) INTO matches_private;
|
||||
|
||||
NEW.is_private := matches_private;
|
||||
NEW.visibility_ok := (
|
||||
(COALESCE(cardinality(NEW.path_hashes), 0) = 0 OR NEW.path_hash_size_bytes BETWEEN 1 AND 3)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM unnest(COALESCE(NEW.path_hashes, ARRAY[]::text[])) AS path_hash
|
||||
WHERE path_hash IS NULL
|
||||
OR length(path_hash) <> NEW.path_hash_size_bytes * 2
|
||||
OR path_hash !~ '^[0-9A-Fa-f]+$'
|
||||
)
|
||||
AND matches_private IS NOT TRUE
|
||||
);
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
DROP TRIGGER IF EXISTS packets_classify_privacy ON packets;
|
||||
CREATE TRIGGER packets_classify_privacy
|
||||
BEFORE INSERT ON packets
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION classify_packet_privacy();
|
||||
@@ -227,6 +227,11 @@ test('packet batch atomically coalesces observer, sighting, and stats writes', {
|
||||
const now = Date.now();
|
||||
let statementCount = 0;
|
||||
configurePacketBatch(async (text, params) => {
|
||||
if (text.includes('jsonb_agg(jsonb_build_object')) {
|
||||
// Private-prefix cache refresh is bookkeeping, not part of the atomic
|
||||
// batch statement (same convention as packetBatch.test.ts).
|
||||
return pool.query(text, params);
|
||||
}
|
||||
statementCount += 1;
|
||||
return pool.query(text, params);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
# CI-only data-plane overlay.
|
||||
#
|
||||
# The live host runs the long-lived data-plane services (timescaledb, mosquitto,
|
||||
# redis, mosquitto-reloader) as a separate meshcore-infra project on the shared
|
||||
# bridge network. GitHub-hosted runners have no such project, so the compose
|
||||
# validation and empty-volume smoke test need the definitions here.
|
||||
#
|
||||
# Never use this file for a real deployment; it exists purely so `ci.yml` can
|
||||
# merge a self-contained stack with `docker compose -f docker-compose.yml
|
||||
# -f docker-compose.ci.yml`.
|
||||
|
||||
services:
|
||||
timescaledb:
|
||||
image: timescale/timescaledb@sha256:22e8a5ae7aef121d1537afe946dd7cc5deeeb63ab36ce19849d671bd3b663509
|
||||
logging: &json-log-limits
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
mem_limit: "4g"
|
||||
memswap_limit: "4g"
|
||||
stop_grace_period: 2m
|
||||
# The image currently tunes timescaledb.max_background_workers to 16.
|
||||
# Leave headroom for the Timescale launcher/schedulers, logical replication,
|
||||
# and a policy worker so scheduled jobs do not fail their first launch.
|
||||
command:
|
||||
- postgres
|
||||
- -c
|
||||
- max_connections=300
|
||||
- -c
|
||||
- max_worker_processes=${POSTGRES_MAX_WORKER_PROCESSES:-24}
|
||||
- -c
|
||||
- max_parallel_workers_per_gather=0
|
||||
environment:
|
||||
POSTGRES_DB: ${POSTGRES_DB:-meshcore}
|
||||
POSTGRES_USER: ${POSTGRES_USER:-meshcore}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD required}
|
||||
volumes:
|
||||
- ./backend/src/db/schema/base.sql:/docker-entrypoint-initdb.d/001_schema.sql:ro
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-meshcore} -d ${POSTGRES_DB:-meshcore}"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 10
|
||||
start_period: 30s
|
||||
|
||||
mosquitto:
|
||||
image: eclipse-mosquitto@sha256:9cfdd46ad59f3e3e5f592f6baf57ab23e1ad00605509d0f5c1e9b179c5314d87
|
||||
logging: *json-log-limits
|
||||
mem_limit: "128m"
|
||||
memswap_limit: "128m"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pgrep mosquitto >/dev/null && nc -z 127.0.0.1 9001"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
volumes:
|
||||
- ./mosquitto:/mosquitto/config
|
||||
- mosquitto_data:/mosquitto/data
|
||||
- mosquitto_log:/mosquitto/log
|
||||
|
||||
mosquitto-reloader:
|
||||
image: ${MOSQUITTO_RELOADER_IMAGE:-meshcore-analytics-mosquitto-reloader:local}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.mosquitto-reloader
|
||||
args:
|
||||
SOURCE_REVISION: ${SOURCE_REVISION:-local}
|
||||
restart: "no"
|
||||
logging: *json-log-limits
|
||||
mem_limit: "64m"
|
||||
memswap_limit: "64m"
|
||||
pid: "service:mosquitto"
|
||||
# Match the broker UID inside the shared PID namespace so SIGHUP can be
|
||||
# delivered without granting CAP_KILL or root.
|
||||
user: "1883:1883"
|
||||
cap_drop:
|
||||
- ALL
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
read_only: true
|
||||
environment:
|
||||
OWNER_ACL_RELOAD_TOKEN: ${JWT_SECRET:?JWT_SECRET required}
|
||||
MOSQUITTO_LOG_PATH: /mosquitto/log/mosquitto.log
|
||||
volumes:
|
||||
- mosquitto_log:/mosquitto/log
|
||||
depends_on:
|
||||
mosquitto:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8080/healthz', timeout=2).read()"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
redis:
|
||||
image: redis@sha256:8b81dd37ff027bec4e516d41acfbe9fe2460070dc6d4a4570a2ac5b9d59df065
|
||||
logging: *json-log-limits
|
||||
mem_limit: "512m"
|
||||
memswap_limit: "512m"
|
||||
command:
|
||||
- redis-server
|
||||
- --requirepass
|
||||
- ${REDIS_PASSWORD:?REDIS_PASSWORD required}
|
||||
- --maxmemory
|
||||
- 448mb
|
||||
- --maxmemory-policy
|
||||
- noeviction
|
||||
- --appendonly
|
||||
- "yes"
|
||||
- --appendfsync
|
||||
- everysec
|
||||
environment:
|
||||
REDIS_PASSWORD: ${REDIS_PASSWORD}
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "redis-cli -a $REDIS_PASSWORD --no-auth-warning ping"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
@@ -52,6 +52,9 @@ services:
|
||||
DATABASE_URL: postgresql://${POSTGRES_USER:-meshcore}:${POSTGRES_PASSWORD}@timescaledb:5432/${POSTGRES_DB:-meshcore}
|
||||
DATABASE_STATEMENT_TIMEOUT_MS: 0
|
||||
NODE_ENV: production
|
||||
depends_on:
|
||||
timescaledb:
|
||||
condition: service_healthy
|
||||
backend:
|
||||
<<: *internal-hosts
|
||||
image: ${BACKEND_IMAGE:-meshcore-analytics-backend:local}
|
||||
|
||||
@@ -125,8 +125,8 @@ test('public site exposes its primary journeys', async ({ page }) => {
|
||||
await page.getByRole('link', { name: 'Topology' }).click();
|
||||
await expect(page).toHaveURL(/\/topology$/);
|
||||
await expect(page.getByRole('heading', { name: 'Repeater topology' })).toBeVisible();
|
||||
await expect(page.getByLabel('Geographic repeater topology graph')).toBeVisible();
|
||||
await expect(page.getByRole('group', { name: '2 positioned repeaters and 1 links' })).toBeVisible();
|
||||
await expect(page.getByLabel('Geographic repeater topology map')).toBeVisible();
|
||||
await expect(page.getByText('2 mapped repeaters · 2 observed relationships')).toBeVisible();
|
||||
|
||||
await expect(page.getByRole('link', { name: 'Health', exact: true })).toHaveCount(0);
|
||||
});
|
||||
|
||||
@@ -11,10 +11,16 @@ log_type notice
|
||||
connection_messages true
|
||||
log_timestamp true
|
||||
|
||||
# WebSocket listener only (no plain TCP MQTT)
|
||||
# WebSocket listener only (no plain TCP MQTT) for external clients.
|
||||
listener 9001
|
||||
protocol websockets
|
||||
|
||||
# Internal-only TCP listener used by the CI smoke test (mosquitto_pub) and the
|
||||
# Prometheus mosquitto exporter. Compose does not publish this port on the
|
||||
# host, and the normal password/ACL policy still applies.
|
||||
listener 1883
|
||||
protocol mqtt
|
||||
|
||||
allow_anonymous false
|
||||
password_file /mosquitto/config/passwd
|
||||
acl_file /mosquitto/config/acl
|
||||
|
||||
@@ -3,7 +3,7 @@ FROM ghcr.io/osgeo/gdal:ubuntu-full-3.13.2@sha256:de280a240ef4309e6fb64b943e0472
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
python3-pip python3-venv \
|
||||
python3-pip python3-venv python3-shapely \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Venv with system-site-packages so GDAL Python bindings + numpy are inherited
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
redis==5.0.8
|
||||
psycopg2-binary==2.9.10
|
||||
shapely==2.0.6
|
||||
psycopg2-binary==2.9.11
|
||||
requests==2.32.3
|
||||
prometheus-client==0.21.1
|
||||
# Pin numpy to the GDAL base image's system numpy (1.26.4). The osgeo gdal_array
|
||||
# C extension is built against that ABI; letting scipy pull numpy 2.x into the
|
||||
# venv breaks `from osgeo import gdal_array` ("numpy.core.multiarray failed to
|
||||
# import") and thus all elevation sampling / coverage computation.
|
||||
numpy==1.26.4
|
||||
scipy==1.13.1
|
||||
# shapely is inherited from the base image's python3-shapely apt package
|
||||
# (built for the image's Python 3.14 / GEOS 3.14 ABI). No pip wheel exists for
|
||||
# cp314, and source builds are incompatible with GEOS 3.14 headers, so it must
|
||||
# NOT be listed here.
|
||||
# Pin numpy to the GDAL base image's system numpy (2.3.5). The osgeo gdal_array
|
||||
# C extension is built against that ABI; letting scipy pull another numpy into
|
||||
# the venv breaks `from osgeo import gdal_array` ("numpy.core.multiarray failed
|
||||
# to import") and thus all elevation sampling / coverage computation.
|
||||
numpy==2.3.5
|
||||
scipy==1.16.3
|
||||
@@ -107,120 +107,19 @@ idna==3.18 \
|
||||
--hash=sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2 \
|
||||
--hash=sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848
|
||||
# via requests
|
||||
numpy==1.26.4 \
|
||||
--hash=sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b \
|
||||
--hash=sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818 \
|
||||
--hash=sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20 \
|
||||
--hash=sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0 \
|
||||
--hash=sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010 \
|
||||
--hash=sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a \
|
||||
--hash=sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea \
|
||||
--hash=sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c \
|
||||
--hash=sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71 \
|
||||
--hash=sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110 \
|
||||
--hash=sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be \
|
||||
--hash=sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a \
|
||||
--hash=sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a \
|
||||
--hash=sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5 \
|
||||
--hash=sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed \
|
||||
--hash=sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd \
|
||||
--hash=sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c \
|
||||
--hash=sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e \
|
||||
--hash=sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0 \
|
||||
--hash=sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c \
|
||||
--hash=sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a \
|
||||
--hash=sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b \
|
||||
--hash=sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0 \
|
||||
--hash=sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6 \
|
||||
--hash=sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2 \
|
||||
--hash=sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a \
|
||||
--hash=sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30 \
|
||||
--hash=sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218 \
|
||||
--hash=sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5 \
|
||||
--hash=sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07 \
|
||||
--hash=sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2 \
|
||||
--hash=sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4 \
|
||||
--hash=sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764 \
|
||||
--hash=sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef \
|
||||
--hash=sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3 \
|
||||
--hash=sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f
|
||||
numpy==2.3.5 \
|
||||
--hash=sha256:fffe29a1ef00883599d1dc2c51aa2e5d80afe49523c261a74933df395c15c520 \
|
||||
--hash=sha256:727fd05b57df37dc0bcf1a27767a3d9a78cbbc92822445f32cc3436ba797337b
|
||||
# via
|
||||
# -r requirements.in
|
||||
# scipy
|
||||
# shapely
|
||||
prometheus-client==0.21.1 \
|
||||
--hash=sha256:252505a722ac04b0456be05c05f75f45d760c2911ffc45f2a06bcaed9f3ae3fb \
|
||||
--hash=sha256:594b45c410d6f4f8888940fe80b5cc2521b305a1fafe1c58609ef715a001f301
|
||||
# via -r requirements.in
|
||||
psycopg2-binary==2.9.10 \
|
||||
--hash=sha256:04392983d0bb89a8717772a193cfaac58871321e3ec69514e1c4e0d4957b5aff \
|
||||
--hash=sha256:056470c3dc57904bbf63d6f534988bafc4e970ffd50f6271fc4ee7daad9498a5 \
|
||||
--hash=sha256:0ea8e3d0ae83564f2fc554955d327fa081d065c8ca5cc6d2abb643e2c9c1200f \
|
||||
--hash=sha256:155e69561d54d02b3c3209545fb08938e27889ff5a10c19de8d23eb5a41be8a5 \
|
||||
--hash=sha256:18c5ee682b9c6dd3696dad6e54cc7ff3a1a9020df6a5c0f861ef8bfd338c3ca0 \
|
||||
--hash=sha256:19721ac03892001ee8fdd11507e6a2e01f4e37014def96379411ca99d78aeb2c \
|
||||
--hash=sha256:1a6784f0ce3fec4edc64e985865c17778514325074adf5ad8f80636cd029ef7c \
|
||||
--hash=sha256:2286791ececda3a723d1910441c793be44625d86d1a4e79942751197f4d30341 \
|
||||
--hash=sha256:230eeae2d71594103cd5b93fd29d1ace6420d0b86f4778739cb1a5a32f607d1f \
|
||||
--hash=sha256:245159e7ab20a71d989da00f280ca57da7641fa2cdcf71749c193cea540a74f7 \
|
||||
--hash=sha256:26540d4a9a4e2b096f1ff9cce51253d0504dca5a85872c7f7be23be5a53eb18d \
|
||||
--hash=sha256:270934a475a0e4b6925b5f804e3809dd5f90f8613621d062848dd82f9cd62007 \
|
||||
--hash=sha256:27422aa5f11fbcd9b18da48373eb67081243662f9b46e6fd07c3eb46e4535142 \
|
||||
--hash=sha256:2ad26b467a405c798aaa1458ba09d7e2b6e5f96b1ce0ac15d82fd9f95dc38a92 \
|
||||
--hash=sha256:2b3d2491d4d78b6b14f76881905c7a8a8abcf974aad4a8a0b065273a0ed7a2cb \
|
||||
--hash=sha256:2ce3e21dc3437b1d960521eca599d57408a695a0d3c26797ea0f72e834c7ffe5 \
|
||||
--hash=sha256:30e34c4e97964805f715206c7b789d54a78b70f3ff19fbe590104b71c45600e5 \
|
||||
--hash=sha256:3216ccf953b3f267691c90c6fe742e45d890d8272326b4a8b20850a03d05b7b8 \
|
||||
--hash=sha256:32581b3020c72d7a421009ee1c6bf4a131ef5f0a968fab2e2de0c9d2bb4577f1 \
|
||||
--hash=sha256:35958ec9e46432d9076286dda67942ed6d968b9c3a6a2fd62b48939d1d78bf68 \
|
||||
--hash=sha256:3abb691ff9e57d4a93355f60d4f4c1dd2d68326c968e7db17ea96df3c023ef73 \
|
||||
--hash=sha256:3c18f74eb4386bf35e92ab2354a12c17e5eb4d9798e4c0ad3a00783eae7cd9f1 \
|
||||
--hash=sha256:3c4745a90b78e51d9ba06e2088a2fe0c693ae19cc8cb051ccda44e8df8a6eb53 \
|
||||
--hash=sha256:3c4ded1a24b20021ebe677b7b08ad10bf09aac197d6943bfe6fec70ac4e4690d \
|
||||
--hash=sha256:3e9c76f0ac6f92ecfc79516a8034a544926430f7b080ec5a0537bca389ee0906 \
|
||||
--hash=sha256:48b338f08d93e7be4ab2b5f1dbe69dc5e9ef07170fe1f86514422076d9c010d0 \
|
||||
--hash=sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2 \
|
||||
--hash=sha256:512d29bb12608891e349af6a0cccedce51677725a921c07dba6342beaf576f9a \
|
||||
--hash=sha256:5a507320c58903967ef7384355a4da7ff3f28132d679aeb23572753cbf2ec10b \
|
||||
--hash=sha256:5c370b1e4975df846b0277b4deba86419ca77dbc25047f535b0bb03d1a544d44 \
|
||||
--hash=sha256:6b269105e59ac96aba877c1707c600ae55711d9dcd3fc4b5012e4af68e30c648 \
|
||||
--hash=sha256:6d4fa1079cab9018f4d0bd2db307beaa612b0d13ba73b5c6304b9fe2fb441ff7 \
|
||||
--hash=sha256:6dc08420625b5a20b53551c50deae6e231e6371194fa0651dbe0fb206452ae1f \
|
||||
--hash=sha256:73aa0e31fa4bb82578f3a6c74a73c273367727de397a7a0f07bd83cbea696baa \
|
||||
--hash=sha256:7559bce4b505762d737172556a4e6ea8a9998ecac1e39b5233465093e8cee697 \
|
||||
--hash=sha256:79625966e176dc97ddabc142351e0409e28acf4660b88d1cf6adb876d20c490d \
|
||||
--hash=sha256:7a813c8bdbaaaab1f078014b9b0b13f5de757e2b5d9be6403639b298a04d218b \
|
||||
--hash=sha256:7b2c956c028ea5de47ff3a8d6b3cc3330ab45cf0b7c3da35a2d6ff8420896526 \
|
||||
--hash=sha256:7f4152f8f76d2023aac16285576a9ecd2b11a9895373a1f10fd9db54b3ff06b4 \
|
||||
--hash=sha256:7f5d859928e635fa3ce3477704acee0f667b3a3d3e4bb109f2b18d4005f38287 \
|
||||
--hash=sha256:851485a42dbb0bdc1edcdabdb8557c09c9655dfa2ca0460ff210522e073e319e \
|
||||
--hash=sha256:8608c078134f0b3cbd9f89b34bd60a943b23fd33cc5f065e8d5f840061bd0673 \
|
||||
--hash=sha256:880845dfe1f85d9d5f7c412efea7a08946a46894537e4e5d091732eb1d34d9a0 \
|
||||
--hash=sha256:8aabf1c1a04584c168984ac678a668094d831f152859d06e055288fa515e4d30 \
|
||||
--hash=sha256:8aecc5e80c63f7459a1a2ab2c64df952051df196294d9f739933a9f6687e86b3 \
|
||||
--hash=sha256:8cd9b4f2cfab88ed4a9106192de509464b75a906462fb846b936eabe45c2063e \
|
||||
--hash=sha256:8de718c0e1c4b982a54b41779667242bc630b2197948405b7bd8ce16bcecac92 \
|
||||
--hash=sha256:9440fa522a79356aaa482aa4ba500b65f28e5d0e63b801abf6aa152a29bd842a \
|
||||
--hash=sha256:b5f86c56eeb91dc3135b3fd8a95dc7ae14c538a2f3ad77a19645cf55bab1799c \
|
||||
--hash=sha256:b73d6d7f0ccdad7bc43e6d34273f70d587ef62f824d7261c4ae9b8b1b6af90e8 \
|
||||
--hash=sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909 \
|
||||
--hash=sha256:c3cc28a6fd5a4a26224007712e79b81dbaee2ffb90ff406256158ec4d7b52b47 \
|
||||
--hash=sha256:ce5ab4bf46a211a8e924d307c1b1fcda82368586a19d0a24f8ae166f5c784864 \
|
||||
--hash=sha256:d00924255d7fc916ef66e4bf22f354a940c67179ad3fd7067d7a0a9c84d2fbfc \
|
||||
--hash=sha256:d7cd730dfa7c36dbe8724426bf5612798734bff2d3c3857f36f2733f5bfc7c00 \
|
||||
--hash=sha256:e217ce4d37667df0bc1c397fdcd8de5e81018ef305aed9415c3b093faaeb10fb \
|
||||
--hash=sha256:e3923c1d9870c49a2d44f795df0c889a22380d36ef92440ff618ec315757e539 \
|
||||
--hash=sha256:e5720a5d25e3b99cd0dc5c8a440570469ff82659bb09431c1439b92caf184d3b \
|
||||
--hash=sha256:e8b58f0a96e7a1e341fc894f62c1177a7c83febebb5ff9123b579418fdc8a481 \
|
||||
--hash=sha256:e984839e75e0b60cfe75e351db53d6db750b00de45644c5d1f7ee5d1f34a1ce5 \
|
||||
--hash=sha256:eb09aa7f9cecb45027683bb55aebaaf45a0df8bf6de68801a6afdc7947bb09d4 \
|
||||
--hash=sha256:ec8a77f521a17506a24a5f626cb2aee7850f9b69a0afe704586f63a464f3cd64 \
|
||||
--hash=sha256:ecced182e935529727401b24d76634a357c71c9275b356efafd8a2a91ec07392 \
|
||||
--hash=sha256:ee0e8c683a7ff25d23b55b11161c2663d4b099770f6085ff0a20d4505778d6b4 \
|
||||
--hash=sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1 \
|
||||
--hash=sha256:f758ed67cab30b9a8d2833609513ce4d3bd027641673d4ebc9c067e4d208eec1 \
|
||||
--hash=sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567 \
|
||||
--hash=sha256:ffe8ed017e4ed70f68b7b371d84b7d4a790368db9203dfc2d222febd3a9c8863
|
||||
psycopg2-binary==2.9.11 \
|
||||
--hash=sha256:47f212c1d3be608a12937cc131bd85502954398aaa1320cb4c14421a0ffccf4c \
|
||||
--hash=sha256:9b52a3f9bb540a3e4ec0f6ba6d31339727b2950c9772850d6545b7eae0b9d7c5
|
||||
# via -r requirements.in
|
||||
redis==5.0.8 \
|
||||
--hash=sha256:0c5b10d387568dfe0698c6fad6615750c24170e548ca2deac10c649d463e9870 \
|
||||
@@ -230,76 +129,9 @@ requests==2.32.3 \
|
||||
--hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \
|
||||
--hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6
|
||||
# via -r requirements.in
|
||||
scipy==1.13.1 \
|
||||
--hash=sha256:017367484ce5498445aade74b1d5ab377acdc65e27095155e448c88497755a5d \
|
||||
--hash=sha256:095a87a0312b08dfd6a6155cbbd310a8c51800fc931b8c0b84003014b874ed3c \
|
||||
--hash=sha256:20335853b85e9a49ff7572ab453794298bcf0354d8068c5f6775a0eabf350aca \
|
||||
--hash=sha256:27e52b09c0d3a1d5b63e1105f24177e544a222b43611aaf5bc44d4a0979e32f9 \
|
||||
--hash=sha256:2831f0dc9c5ea9edd6e51e6e769b655f08ec6db6e2e10f86ef39bd32eb11da54 \
|
||||
--hash=sha256:2ac65fb503dad64218c228e2dc2d0a0193f7904747db43014645ae139c8fad16 \
|
||||
--hash=sha256:392e4ec766654852c25ebad4f64e4e584cf19820b980bc04960bca0b0cd6eaa2 \
|
||||
--hash=sha256:436bbb42a94a8aeef855d755ce5a465479c721e9d684de76bf61a62e7c2b81d5 \
|
||||
--hash=sha256:45484bee6d65633752c490404513b9ef02475b4284c4cfab0ef946def50b3f59 \
|
||||
--hash=sha256:54f430b00f0133e2224c3ba42b805bfd0086fe488835effa33fa291561932326 \
|
||||
--hash=sha256:5713f62f781eebd8d597eb3f88b8bf9274e79eeabf63afb4a737abc6c84ad37b \
|
||||
--hash=sha256:5d72782f39716b2b3509cd7c33cdc08c96f2f4d2b06d51e52fb45a19ca0c86a1 \
|
||||
--hash=sha256:637e98dcf185ba7f8e663e122ebf908c4702420477ae52a04f9908707456ba4d \
|
||||
--hash=sha256:8335549ebbca860c52bf3d02f80784e91a004b71b059e3eea9678ba994796a24 \
|
||||
--hash=sha256:949ae67db5fa78a86e8fa644b9a6b07252f449dcf74247108c50e1d20d2b4627 \
|
||||
--hash=sha256:a014c2b3697bde71724244f63de2476925596c24285c7a637364761f8710891c \
|
||||
--hash=sha256:a78b4b3345f1b6f68a763c6e25c0c9a23a9fd0f39f5f3d200efe8feda560a5fa \
|
||||
--hash=sha256:cdd7dacfb95fea358916410ec61bbc20440f7860333aee6d882bb8046264e949 \
|
||||
--hash=sha256:cfa31f1def5c819b19ecc3a8b52d28ffdcc7ed52bb20c9a7589669dd3c250989 \
|
||||
--hash=sha256:d533654b7d221a6a97304ab63c41c96473ff04459e404b83275b60aa8f4b7004 \
|
||||
--hash=sha256:d605e9c23906d1994f55ace80e0125c587f96c020037ea6aa98d01b4bd2e222f \
|
||||
--hash=sha256:de3ade0e53bc1f21358aa74ff4830235d716211d7d077e340c7349bc3542e884 \
|
||||
--hash=sha256:e89369d27f9e7b0884ae559a3a956e77c02114cc60a6058b4e5011572eea9299 \
|
||||
--hash=sha256:eccfa1906eacc02de42d70ef4aecea45415f5be17e72b61bafcfd329bdc52e94 \
|
||||
--hash=sha256:f26264b282b9da0952a024ae34710c2aff7d27480ee91a2e82b7b7073c24722f
|
||||
# via -r requirements.in
|
||||
shapely==2.0.6 \
|
||||
--hash=sha256:0334bd51828f68cd54b87d80b3e7cee93f249d82ae55a0faf3ea21c9be7b323a \
|
||||
--hash=sha256:1bbc783529a21f2bd50c79cef90761f72d41c45622b3e57acf78d984c50a5d13 \
|
||||
--hash=sha256:2423f6c0903ebe5df6d32e0066b3d94029aab18425ad4b07bf98c3972a6e25a1 \
|
||||
--hash=sha256:28f87cdf5308a514763a5c38de295544cb27429cfa655d50ed8431a4796090c4 \
|
||||
--hash=sha256:29a34e068da2d321e926b5073539fd2a1d4429a2c656bd63f0bd4c8f5b236d0b \
|
||||
--hash=sha256:2ad2fae12dca8d2b727fa12b007e46fbc522148a584f5d6546c539f3464dccde \
|
||||
--hash=sha256:2b542d7f1dbb89192d3512c52b679c822ba916f93479fa5d4fc2fe4fa0b3c9e8 \
|
||||
--hash=sha256:2c665a0301c645615a107ff7f52adafa2153beab51daf34587170d85e8ba6805 \
|
||||
--hash=sha256:2de00c3bfa80d6750832bde1d9487e302a6dd21d90cb2f210515cefdb616e5f5 \
|
||||
--hash=sha256:392f66f458a0a2c706254f473290418236e52aa4c9b476a072539d63a2460595 \
|
||||
--hash=sha256:3a82d58a1134d5e975f19268710e53bddd9c473743356c90d97ce04b73e101ee \
|
||||
--hash=sha256:3ec3a0eab496b5e04633a39fa3d5eb5454628228201fb24903d38174ee34565e \
|
||||
--hash=sha256:42805ef90783ce689a4dde2b6b2f261e2c52609226a0438d882e3ced40bb3013 \
|
||||
--hash=sha256:42fd4cd4834747e4990227e4cbafb02242c0cffe9ce7ef9971f53ac52d80d55f \
|
||||
--hash=sha256:44246d30124a4f1a638a7d5419149959532b99dfa25b54393512e6acc9c211ac \
|
||||
--hash=sha256:537c4b2716d22c92036d00b34aac9d3775e3691f80c7aa517c2c290351f42cd8 \
|
||||
--hash=sha256:5aeb0f51a9db176da9a30cb2f4329b6fbd1e26d359012bb0ac3d3c7781667a9e \
|
||||
--hash=sha256:665990c84aece05efb68a21b3523a6b2057e84a1afbef426ad287f0796ef8a48 \
|
||||
--hash=sha256:6d2cb146191a47bd0cee8ff5f90b47547b82b6345c0d02dd8b25b88b68af62d7 \
|
||||
--hash=sha256:7060566bc4888b0c8ed14b5d57df8a0ead5c28f9b69fb6bed4476df31c51b0af \
|
||||
--hash=sha256:81d9dfe155f371f78c8d895a7b7f323bb241fb148d848a2bf2244f79213123fe \
|
||||
--hash=sha256:837d395fac58aa01aa544495b97940995211e3e25f9aaf87bc3ba5b3a8cd1ac7 \
|
||||
--hash=sha256:83b94a44ab04a90e88be69e7ddcc6f332da7c0a0ebb1156e1c4f568bbec983c3 \
|
||||
--hash=sha256:8b3b818c4407eaa0b4cb376fd2305e20ff6df757bf1356651589eadc14aab41b \
|
||||
--hash=sha256:98fea108334be345c283ce74bf064fa00cfdd718048a8af7343c59eb40f59726 \
|
||||
--hash=sha256:997f6159b1484059ec239cacaa53467fd8b5564dabe186cd84ac2944663b0bf6 \
|
||||
--hash=sha256:9a7a78b0d51257a367ee115f4d41ca4d46edbd0dd280f697a8092dd3989867b2 \
|
||||
--hash=sha256:b02154b3e9d076a29a8513dffcb80f047a5ea63c897c0cd3d3679f29363cf7e5 \
|
||||
--hash=sha256:b3304883bd82d44be1b27a9d17f1167fda8c7f5a02a897958d86c59ec69b705e \
|
||||
--hash=sha256:b3dc9fb0eb56498912025f5eb352b5126f04801ed0e8bdbd867d21bdbfd7cbd0 \
|
||||
--hash=sha256:c02eb6bf4cfb9fe6568502e85bb2647921ee49171bcd2d4116c7b3109724ef9b \
|
||||
--hash=sha256:c6d88ade96bf02f6bfd667ddd3626913098e243e419a0325ebef2bbd481d1eb6 \
|
||||
--hash=sha256:cec9193519940e9d1b86a3b4f5af9eb6910197d24af02f247afbfb47bcb3fab0 \
|
||||
--hash=sha256:d37d070da9e0e0f0a530a621e17c0b8c3c9d04105655132a87cfff8bd77cc4c2 \
|
||||
--hash=sha256:d93b7e0e71c9f095e09454bf18dad5ea716fb6ced5df3cb044564a00723f339d \
|
||||
--hash=sha256:e1c84c3f53144febf6af909d6b581bc05e8785d57e27f35ebaa5c1ab9baba13b \
|
||||
--hash=sha256:e3fdef0a1794a8fe70dc1f514440aa34426cc0ae98d9a1027fb299d45741c381 \
|
||||
--hash=sha256:eba5bae271d523c938274c61658ebc34de6c4b33fdf43ef7e938b5776388c1be \
|
||||
--hash=sha256:ed5867e598a9e8ac3291da6cc9baa62ca25706eea186117034e8ec0ea4355653 \
|
||||
--hash=sha256:f32c23d2f43d54029f986479f7c1f6e09c6b3a19353a3833c2ffb226fb63a855 \
|
||||
--hash=sha256:fa7468e4f5b92049c0f36d63c3e309f85f2775752e076378e36c6387245c5462 \
|
||||
--hash=sha256:fbb7bf02a7542dba55129062570211cfb0defa05386409b3e306c39612e7fbcc
|
||||
scipy==1.16.3 \
|
||||
--hash=sha256:d3837938ae715fc0fe3c39c0202de3a8853aff22ca66781ddc2ade7554b7e2cc \
|
||||
--hash=sha256:8b3c820ddb80029fe9f43d61b81d8b488d3ef8ca010d15122b152db77dc94c22
|
||||
# via -r requirements.in
|
||||
urllib3==2.7.0 \
|
||||
--hash=sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c \
|
||||
|
||||
Reference in new issue
Block a user