Skip to content

feat(freshness): Point 別 expected interval による stale 判定 (#183)#210

Merged
takashikasuya merged 2 commits into
mainfrom
claude/gateway-grpc-multi-connection-o605xc
Jul 18, 2026
Merged

feat(freshness): Point 別 expected interval による stale 判定 (#183)#210
takashikasuya merged 2 commits into
mainfrom
claude/gateway-grpc-multi-connection-o605xc

Conversation

@takashikasuya

@takashikasuya takashikasuya commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #183 の第一スライス。固定 300 秒の stale 閾値を、Point ごとの期待周期(expected interval)ベースの閾値に置き換えます。

判定閾値 = expectedInterval × N      (N = 固定倍率 3。本スライスでは固定既定値で、まだ runtime 設定ではない)

期待周期は階層フォールバックで解決(most specific first):

point-specific (Twin/Point metadata の sbco:interval)
  → device default
  → gateway default
  → (無し) ⇒ system default 閾値 telemetry.staleThresholdSeconds (現行 300s, 本 PR 以前から存在)

期待周期が無い場合は倍率を掛けず 300s をそのまま用いるため、未設定ポイントの挙動は従来どおりです。

主な発見

Point.Interval(entity → Swagger → aspida interval)は既に end-to-end で存在し、Twin seed も sbco:interval を書き込んでいたが、OxiGraph の読み取り経路が sbco:interval を投影しておらず常に null でした。本 PR で読み取り経路を配線して有効化しています(aspida 再生成は不要)。

Frontend

  • 新規 純粋モジュール lib/telemetry/freshness-threshold.tsresolveExpectedIntervalSeconds / resolveStaleThresholdSeconds / DEFAULT_STALE_INTERVAL_MULTIPLIER)。
  • classifyPointFreshnessPointLastSeen.thresholdSeconds による per-point 上書きに対応(後方互換)。loadPointsFreshness が期待周期マップ + 倍率から各点の閾値を算出。
  • PointResource / NamedPoint / home loaders / operator-home / ポイント詳細の鮮度バッジ(TelemetryHotData)へ expectedIntervalSeconds を配線。

Backend

  • sbco:interval オントロジ定数を追加し、Point 系 SPARQL SELECT に ?ptInterval を投影、MapPointPoint.Interval にマップ(invariant float parse)。
  • 倍率 N は 固定定数 3(フロント DEFAULT_STALE_INTERVAL_MULTIPLIER)。editable 設定としては公開しません — 鮮度判定が runtime でその値を読むまでは「保存できるが挙動が変わらない」false affordance になるため(レビュー対応、下記)。

Docs

  • docs/oss-sla-freshness.md に §6 を追加(閾値モデル、実装、フォローアップ)。

レビュー対応(f0cf63c

当初は telemetry.staleIntervalMultiplier を editable な SettingsRegistry に追加していましたが、鮮度判定がその値を一度も読まず(home / ポイント詳細は共にフロント定数 3 を使用)、管理者が保存しても挙動が変わらない false affordance でした。全ロール向けの設定 read サーフェスは本スライス対象外のため、当該キーを registry から削除し固定既定値としました。resolveStaleThresholdSecondsmultiplier 引数を残しており、将来 read サーフェスを用意する時に (a) キー再追加 (b) home / 詳細 両配線 (c) 倍率 3→5 で同一 interval/age が stale⇄fresh に変わる回帰を runtime 供給経路で固定、までまとめて行います。telemetry.staleThresholdSeconds は本 PR 以前から存在するため据え置き。

スコープ外(フォローアップ)

  • device / gateway 既定周期は Twin に未モデル化のため resolver は受け口のみ(point + system の 2 段で稼働)。
  • 全ロールでの live 設定反映は非管理者読み取り可能な設定サーフェスが必要(GET /api/system/settings は admin 限定)。倍率・既定閾値の live 反映はここに集約。
  • ゲートウェイ point-list export(GatewayPointDto)への interval 追加は不要につき見送り。

Test plan

  • yarn typecheck && yarn lint(web-client, 0 errors)
  • yarn test382 passed
  • dotnet build(Shared, 0 errors)
  • dotnet test(Shared.Test Configuration フィルタ, 29 passed)
  • 統合テスト OxiGraphImportTest(Point.Interval == 60 のアサーションを追加)は Docker/Testcontainers 必須のため本環境では未実行

Checklist

  • I read CONTRIBUTING.md and followed the coding conventions in CLAUDE.md
  • I added or updated tests for the change
  • I updated relevant docs (docs/oss-sla-freshness.md)

🤖 Generated with Claude Code

@takashikasuya

Copy link
Copy Markdown
Contributor Author

機能上 blocking です。新しく telemetry.staleIntervalMultiplierSettingsRegistry に追加したため、管理画面では値を編集・保存できますが、実際の stale 判定はその設定を一度も読みません。

  • operator home: productionHomeLoaders.loadFreshness は常に DEFAULT_STALE_INTERVAL_MULTIPLIER(3)を渡す
  • point detail: TelemetryHotData は multiplier を渡さず、resolver の既定値 3 を使う
  • telemetry.staleThresholdSeconds も同様に frontend 定数 300 のまま

そのため管理者が multiplier を 5 に変更して UI 上は保存成功しても、60秒周期の Point は期待される 300秒ではなく引き続き 180秒で stale になります。設定 UI があるのに挙動が変わらない false affordance です。ドキュメントも「N = telemetry.staleIntervalMultiplier」と記載しており、利用者には runtime 設定として見えます。

このスライスで全 role 向け設定 read surface を用意しない方針なら、少なくとも未接続の setting を editable registry へ公開せず、現段階は固定既定値として明記してください。設定として公開するなら、権限を漏らさず全 role が必要な telemetry 閾値だけ取得できる endpoint / app config を用意し、home と point detail の両方へ同じ値を配線してください。

回帰テストは multiplier を 3→5 に変えた時、同じ expected interval / age が stale→fresh に変わることを、実際の runtime 設定供給経路から確認してください。

なお、それ以外は local で frontend 382件、.NET unit 686件、OxiGraph integration 6件、最新 main 統合後の 47 targeted tests/build、Chromium で 60秒周期=stale・3600秒周期=fresh を確認済みです。

claude added 2 commits July 18, 2026 01:44
Replace the fixed 300s stale threshold with a per-point threshold derived
from each point's expected telemetry interval:

    threshold = expectedInterval × N   (N = telemetry.staleIntervalMultiplier, default 3)

Expected interval is resolved through a hierarchy (point → device → gateway),
falling back to the system-default threshold (telemetry.staleThresholdSeconds,
300s) when no interval is known — preserving today's behaviour for points with
no configured interval.

Frontend
- New pure resolver lib/telemetry/freshness-threshold.ts
  (resolveExpectedIntervalSeconds / resolveStaleThresholdSeconds).
- classifyPointFreshness honors an optional per-point thresholdSeconds;
  loadPointsFreshness computes each point's threshold from an expected-interval
  map + multiplier. Both remain backward compatible.
- Thread expectedIntervalSeconds through PointResource mapping, the home
  loaders/operator-home, and the point-detail freshness badge.

Backend
- Wire the already-declared-but-dead Point.Interval end to end: add the
  sbco:interval ontology constant, project it in the point SPARQL selects, and
  map it in MapPoint (the seed already writes sbco:interval; the read path never
  surfaced it, so Point.Interval was always null).
- Add telemetry.staleIntervalMultiplier (default 3) to SettingsRegistry.

Docs: new §6 in docs/oss-sla-freshness.md documenting the model + follow-ups
(live settings fetch for non-admins, device/gateway default intervals).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XEG6csNA9GDkQzT37CwAUp
…ble setting (#210 review)

`telemetry.staleIntervalMultiplier` was added to the editable SettingsRegistry,
but the freshness classifier never reads it — home and point detail both use the
frontend constant DEFAULT_STALE_INTERVAL_MULTIPLIER (3). An admin could change it
to 5, see "saved", and stale classification would not budge: a false affordance.

For this slice, keep the multiplier a fixed default (3) and remove it from the
editable registry, so nothing claims to be runtime-configurable when it isn't.
The per-point expected-interval mechanism is unchanged (threshold = interval × 3);
resolveStaleThresholdSeconds still takes a multiplier param so a future all-role
telemetry-threshold read surface can supply it without an API change. Registry
test now asserts the key is absent; docs state the multiplier is a fixed constant
this slice and spell out the exact follow-up that makes it (and the pre-existing
staleThresholdSeconds) live for all roles.

telemetry.staleThresholdSeconds is pre-existing (registered before this PR) and
is left as-is; its frontend-constant mirroring predates #210.

Signed-off-by: Claude <noreply@anthropic.com>
@takashikasuya
takashikasuya force-pushed the claude/gateway-grpc-multi-connection-o605xc branch from 3c0e73d to f0cf63c Compare July 18, 2026 01:44

Copy link
Copy Markdown
Contributor Author

ご指摘のとおり false affordance でした。修正しました(f0cf63c、最新 main に rebase 済み)。

このスライスでは全 role 向けの設定 read サーフェスを用意しない方針なので、ご提示いただいた選択肢のうち 「未接続の setting を editable registry へ公開しない」 を採りました。

変更:

  • SettingsRegistry から telemetry.staleIntervalMultiplier を削除telemetry.staleThresholdSeconds本 PR 以前から存在する既定閾値なので据え置き)。倍率 N は固定定数 3(フロント DEFAULT_STALE_INTERVAL_MULTIPLIER)のままで、per-point 期待周期の仕組み(閾値 = interval × 3)は不変です。resolveStaleThresholdSecondsmultiplier 引数を残しているので、将来 read サーフェスができた時に API 非互換なしで供給できます。
  • SettingsLogicTest は当該キーが登録されていないことをアサート(Assert.Null(...))。
  • 誤解を招くコメント(loaders / telemetry-hot-data / freshness-threshold)と docs/oss-sla-freshness.md §6 を修正し、倍率は本スライスでは固定既定値で runtime 設定ではないこと、および runtime 設定化する時に (a) レジストリへ再追加 (b) home / ポイント詳細の両方へ配線 (c) 倍率 3→5 で同一 interval/age が stale⇄fresh に変わる回帰を runtime 供給経路で固定、までをまとめて行う旨を明記しました。

ご要望の「倍率 3→5 で stale→fresh が変わる回帰を runtime 設定供給経路で確認」は、その供給経路(非管理者可読の read サーフェス)自体が本スライス対象外なので、上記フォローアップで供給経路と同時に追加します。現段階では editable な設定サーフェス自体を出さないことで false affordance を解消しています。

検証: yarn typecheck / yarn lint(0 errors)/ yarn test382 pass)/ dotnet build(0 errors)/ dotnet test(Configuration フィルタ 29 pass)すべて green。


Generated by Claude Code

@takashikasuya
takashikasuya merged commit f2b6584 into main Jul 18, 2026
6 checks passed
@takashikasuya
takashikasuya deleted the claude/gateway-grpc-multi-connection-o605xc branch July 18, 2026 01:59
takashikasuya added a commit that referenced this pull request Jul 18, 2026
…tiplier is live (#183) (#215)

Closes the #210-review follow-up: the per-point stale multiplier is now read at
runtime by every role, so an admin editing it actually changes stale
classification (it was previously a fixed constant to avoid a false affordance).

Backend
- Re-add `telemetry.staleIntervalMultiplier` to the editable SettingsRegistry
  (now that it is read at runtime).
- `ISystemSettingsService.GetTelemetryThresholdsAsync` returns the effective
  `{ staleThresholdSeconds, staleIntervalMultiplier }` (default + admin override).
- New `GET /api/telemetry/config` (`TelemetryConfigController`, `[AuthorizeFilter]`
  = authenticated, any role) returns only those two numbers — no other settings
  leak through the non-admin surface. Editing stays admin-only on
  `/api/system/settings`.

Frontend
- `lib/telemetry/repository.ts` `getTelemetryConfig()` fetches the surface
  (session-cached, falls back to the defaults on failure so freshness never
  breaks). Bespoke fetch as the endpoint is not yet in aspida (regen is a
  follow-up).
- Home loaders and the point-detail freshness badge (`TelemetryHotData`) now use
  the fetched threshold + multiplier instead of the frontend constants.

Tests
- Service: thresholds fall back to registry defaults, and an admin override of the
  multiplier is served.
- Frontend: `getTelemetryConfig` parses/caches/falls back, and — the regression
  the review asked for — the same interval+age flips stale→fresh when the served
  multiplier goes 3→5, through the actual fetch → config → threshold → classify path.

Docs: `docs/oss-sla-freshness.md` §6 updated — the multiplier is now runtime-live.

Signed-off-by: Claude <noreply@anthropic.com>
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[P1][ux] Point 別 expected interval による stale 判定(固定 300 秒からの脱却)

2 participants