mirror of
https://github.com/ruvnet/RuView
synced 2026-06-17 11:33:19 +00:00
df617145d6
* feat(ADR-262 P3): live RuField surface — RuView sensing speaks RuField on /api/field + /ws/field Wire the P1 `wifi-densepose-rufield` bridge into the live `wifi-densepose-sensing-server` so the governed sensing cycle emits real signed RuField `FieldEvent`s on two additive endpoints. - Cargo: add the `wifi-densepose-rufield` path dep (the single coupling point, ADR-262 §5.4 — no new RuView-internal coupling). - New `src/rufield_surface.rs` (kept out of the 8k-line main.rs): `FieldSurface` holds a dedicated ed25519 `Signer` + a bounded ring of recent events + the `/ws/field` broadcast topic; `GET /api/field` and `GET /ws/field` handlers; a standalone `router()` for isolated testing. - Signer (defers the P2 key decision, ADR-262 §8 Q1): a STANDALONE dev/sensing key from `WDP_RUFIELD_SIGNING_SEED`, else a deterministic dev default with a logged WARN. Reusing the `cog-ha-matter` Ed25519 key is the deferred P2 call — P3 does not pre-empt it. - Tap: at the ESP32 governed-trust cycle (`main.rs` ~5886 observe_cycle / ~5938 SensingUpdate build), `emit_rufield_event` joins the cycle's features/classification/signal_field with the engine's effective_class/demoted trust state into a `SensingSnapshot` and surfaces it via the bridge. Existing endpoints (`/ws/sensing` etc.) are unchanged — purely additive. - Privacy egress: `network_egress_allowed` is fail-closed for an unattended live surface — only P1/P2 leave the box; P0 raw and P3/P4/P5 (identity/biometric/aggregate) are held edge-local. A `Derived` cycle maps to P4/P5 and never surfaces. - No-phantom: `emit` drops no-presence cycles (no fabricated events). Gates (tests/rufield_surface_test.rs, tower::oneshot, 4/0): well-formed signed event (WifiCsi, P2 not P1, is_fusable, real timestamp); empty cycle → no phantom; Derived trust never surfaces; mixed stream surfaces only egress-safe events. Honesty (ADR-262 §0/§6): real plumbing on a live endpoint, NOT accuracy. Single-link CSI with its existing caveats (no validated room-coordinate accuracy); dedicated dev signing key pending the P2 ownership decision; no accuracy claim. Co-Authored-By: claude-flow <ruv@ruv.net> * docs(ADR-262 P3): mark P1+P3 implemented; document /api/field + /ws/field; CHANGELOG - ADR-262 Status → "P1 + P3 implemented"; add a P3 implementation-status block (tap site, endpoints, dedicated dev signer deferring the §8 Q1 key decision, fail-closed egress, gates). Keep the honesty framing: real plumbing on a live endpoint, not accuracy. - CHANGELOG [Unreleased]: add the ADR-262 P3 entry. - user-guide: add `/api/field` to the REST table + a "RuField surface (ADR-262 P3)" section covering `/api/field` + `/ws/field`, the fail-closed P1/P2-only egress, the WDP_RUFIELD_SIGNING_SEED dev key, and the no-accuracy honesty note. Co-Authored-By: claude-flow <ruv@ruv.net> * ci: checkout submodules everywhere + Dockerfile copies vendor/rufield Making wifi-densepose-rufield (ADR-262 bridge) a v2 workspace member means EVERY cargo-on-workspace context must have the vendor/rufield submodule present (cargo loads all member manifests). P1 only fixed the rust-tests job; this adds `submodules: recursive` to all workflow checkouts that run cargo (mqtt-integration was failing on the missing submodule manifest), and makes Dockerfile.rust COPY vendor/rufield/ to /vendor/rufield (matches the bridge's ../../../vendor/rufield path-dep under the collapsed Docker layout). update-submodules.yml left alone (it manages submodules itself). Co-Authored-By: claude-flow <ruv@ruv.net> --------- Co-authored-by: ruv <ruvnet@gmail.com>
152 lines
6.2 KiB
YAML
152 lines
6.2 KiB
YAML
name: GitHub Clone Tracking → data/clone-data.rvf
|
|
|
|
# Persists rolling 14-day clone-traffic snapshots to data/clone-data.rvf in
|
|
# the ruvector JSONL RVF format. GitHub's /traffic/clones endpoint only
|
|
# retains the last 14 days server-side, so without this scheduled scrape
|
|
# the data is gone forever the moment it falls outside the window.
|
|
#
|
|
# Format: JSONL RVF
|
|
# - line 1 is a `metadata` segment that initializes the file
|
|
# - each subsequent run appends one `clone_snapshot` segment carrying the
|
|
# 14-day rollup PLUS per-day breakdown
|
|
# - file is idempotent: per-day entries are keyed by `timestamp` so a
|
|
# downstream reader can dedupe across overlapping snapshot windows
|
|
#
|
|
# Schedule: every 14 days (1st + 15th of each month, ~14-day cadence in
|
|
# practice). Workflow can also be dispatched manually for backfill or test.
|
|
|
|
on:
|
|
schedule:
|
|
# 01:23 UTC on the 1st and 15th of every month — close to 14-day cadence
|
|
# without cron's "every 14 days" monthly-reset weirdness. Picking :23
|
|
# avoids the cron herd on :00.
|
|
- cron: '23 1 1,15 * *'
|
|
workflow_dispatch:
|
|
|
|
permissions:
|
|
contents: write
|
|
|
|
concurrency:
|
|
group: clone-tracking
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
snapshot:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
submodules: recursive
|
|
|
|
- name: Fetch /traffic/clones + /traffic/views from GitHub
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
mkdir -p data
|
|
gh api repos/${{ github.repository }}/traffic/clones > /tmp/clones.json
|
|
gh api repos/${{ github.repository }}/traffic/views > /tmp/views.json
|
|
echo "--- clones rollup ---"
|
|
jq '{count, uniques, days: (.clones | length)}' /tmp/clones.json
|
|
echo "--- views rollup ---"
|
|
jq '{count, uniques, days: (.views | length)}' /tmp/views.json
|
|
|
|
- name: Append snapshot to data/clone-data.rvf
|
|
env:
|
|
REPO: ${{ github.repository }}
|
|
run: |
|
|
set -e
|
|
RVF="data/clone-data.rvf"
|
|
FETCHED_AT=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
|
|
# Initialize the file with a metadata segment on first run.
|
|
if [ ! -f "$RVF" ]; then
|
|
echo "Initializing $RVF with metadata segment"
|
|
jq -n --arg repo "$REPO" --arg ts "$FETCHED_AT" '{
|
|
type: "metadata",
|
|
name: "ruview-clone-traffic-history",
|
|
version: "1.0.0",
|
|
schema: "ruvector.rvf.jsonl/v1",
|
|
format: "github-traffic-snapshots",
|
|
repo: $repo,
|
|
source: "GitHub Traffic API /repos/{repo}/traffic/{clones,views}",
|
|
policy: "GitHub retains only 14 days server-side; this file is the long-term record.",
|
|
segments: ["metadata", "clone_snapshot", "view_snapshot"],
|
|
created_at: $ts,
|
|
custom: {
|
|
cadence: "twice monthly (1st and 15th, ~14-day intervals)",
|
|
idempotency_key: "timestamp (per-day records de-duplicate across overlapping snapshot windows)"
|
|
}
|
|
}' >> "$RVF"
|
|
fi
|
|
|
|
# Append the clone snapshot.
|
|
jq --arg ts "$FETCHED_AT" '{
|
|
type: "clone_snapshot",
|
|
fetched_at: $ts,
|
|
window_count: .count,
|
|
window_uniques: .uniques,
|
|
per_day: .clones
|
|
}' /tmp/clones.json >> "$RVF"
|
|
|
|
# Append the views snapshot (free with the same auth).
|
|
jq --arg ts "$FETCHED_AT" '{
|
|
type: "view_snapshot",
|
|
fetched_at: $ts,
|
|
window_count: .count,
|
|
window_uniques: .uniques,
|
|
per_day: .views
|
|
}' /tmp/views.json >> "$RVF"
|
|
|
|
echo "--- RVF tail (last 4 lines) ---"
|
|
tail -4 "$RVF" | jq -c '{type, fetched_at, window_count, window_uniques}' || true
|
|
echo "--- file size ---"
|
|
wc -l "$RVF"
|
|
|
|
- name: Compute aggregates for the commit summary
|
|
id: agg
|
|
run: |
|
|
# Count distinct per-day entries across all snapshots so we can
|
|
# show "cumulative observed clones" in the commit message.
|
|
python3 - <<'PY'
|
|
import json, os
|
|
path = "data/clone-data.rvf"
|
|
per_day_clones = {}
|
|
per_day_views = {}
|
|
with open(path, encoding="utf-8") as f:
|
|
for line in f:
|
|
if not line.strip():
|
|
continue
|
|
d = json.loads(line)
|
|
if d.get("type") == "clone_snapshot":
|
|
for entry in d.get("per_day", []):
|
|
per_day_clones[entry["timestamp"]] = entry
|
|
elif d.get("type") == "view_snapshot":
|
|
for entry in d.get("per_day", []):
|
|
per_day_views[entry["timestamp"]] = entry
|
|
|
|
tot_clones = sum(e.get("count", 0) for e in per_day_clones.values())
|
|
tot_uniq_clones = sum(e.get("uniques", 0) for e in per_day_clones.values())
|
|
tot_views = sum(e.get("count", 0) for e in per_day_views.values())
|
|
tot_uniq_views = sum(e.get("uniques", 0) for e in per_day_views.values())
|
|
print(f"clone days observed: {len(per_day_clones)} total clones: {tot_clones:,} total unique cloners: {tot_uniq_clones:,}")
|
|
print(f"view days observed: {len(per_day_views)} total views: {tot_views:,} total unique viewers: {tot_uniq_views:,}")
|
|
|
|
with open(os.environ["GITHUB_OUTPUT"], "a") as out:
|
|
out.write(f"clones={tot_clones}\n")
|
|
out.write(f"clone_days={len(per_day_clones)}\n")
|
|
out.write(f"views={tot_views}\n")
|
|
out.write(f"view_days={len(per_day_views)}\n")
|
|
PY
|
|
|
|
- name: Commit + push if changed
|
|
run: |
|
|
git config user.name "github-actions[bot]"
|
|
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
if git diff --quiet data/clone-data.rvf; then
|
|
echo "no changes to commit"
|
|
exit 0
|
|
fi
|
|
git add data/clone-data.rvf
|
|
git commit -m "chore(traffic): clone snapshot — ${{ steps.agg.outputs.clone_days }} days observed → ${{ steps.agg.outputs.clones }} clones, ${{ steps.agg.outputs.view_days }} view-days → ${{ steps.agg.outputs.views }} views"
|
|
git push
|