fix(runtime): keep shared artifacts optional
This commit is contained in:
@@ -64,7 +64,10 @@ separate future gate.
|
|||||||
|
|
||||||
`GET /api/v1/device-plugin-runtimes` exposes the small lifecycle snapshots. The
|
`GET /api/v1/device-plugin-runtimes` exposes the small lifecycle snapshots. The
|
||||||
main health response includes ready/total runtime counts as one component beside
|
main health response includes ready/total runtime counts as one component beside
|
||||||
recording-cache, shared-artifact, media-tool and catalog-reconciler readiness.
|
recording-cache, optional shared-artifact capability, media-tool and
|
||||||
|
catalog-reconciler readiness. A missing optional shared-artifact provider does
|
||||||
|
not degrade the complete local contour.
|
||||||
|
|
||||||
The plugin values still do not claim device connectivity, sensor health,
|
The plugin values still do not claim device connectivity, sensor health,
|
||||||
process containment or durable supervision.
|
process containment or durable supervision.
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ Contour health reads only live contracts:
|
|||||||
- current Mission Runtime state for active device and perception metrics.
|
- current Mission Runtime state for active device and perception metrics.
|
||||||
|
|
||||||
The base health contract now separates local operational readiness from
|
The base health contract now separates local operational readiness from
|
||||||
production shared-artifact readiness and reports bounded cache, catalog
|
optional shared-artifact capability and reports bounded cache, catalog
|
||||||
reconciler, media-tool and Map Gateway configuration states. It performs no
|
reconciler, media-tool and Map Gateway configuration states. It performs no
|
||||||
remote fan-out and never invents Worker 006 or device health.
|
remote fan-out and never invents Worker 006 or device health.
|
||||||
|
|
||||||
|
|||||||
@@ -5,17 +5,18 @@ Status: accepted and implemented
|
|||||||
|
|
||||||
## Decision
|
## Decision
|
||||||
|
|
||||||
Mission Core stores portable recorded-session products in a provider-neutral,
|
Mission Core can exchange portable recorded-session products through an
|
||||||
content-addressed artifact store. The current central provider is the existing
|
optional provider-neutral, content-addressed artifact gateway. The canonical
|
||||||
Synology SMB mount:
|
current operator contour is local and complete without a shared store. An
|
||||||
|
external filesystem provider is an auxiliary transport/prefetch capability,
|
||||||
|
not an operational-readiness dependency and not a production topology
|
||||||
|
decision.
|
||||||
|
|
||||||
```text
|
The historical RAVNOVES00 migration acceptance used an existing SMB-backed
|
||||||
/Volumes/docker/nodedc-mission-core/artifact-store
|
filesystem as one experimental provider. That provider, its mount and its host
|
||||||
```
|
configuration are outside the current Mission Core runtime contract. Mission
|
||||||
|
Core does not own its authentication, does not require it at startup and does
|
||||||
This is product runtime data. It is not a deploy artifact, a Map Gateway cache,
|
not receive a NAS password.
|
||||||
a Device Plane package, or a new Docker service. Authentication remains owned
|
|
||||||
by the operating-system SMB mount; Mission Core does not receive a NAS password.
|
|
||||||
|
|
||||||
Every immutable object is stored under:
|
Every immutable object is stored under:
|
||||||
|
|
||||||
@@ -55,7 +56,7 @@ MISSIONCORE_ARTIFACT_CACHE_MAX_BYTES
|
|||||||
MISSIONCORE_ARTIFACT_CACHE_FREE_SPACE_RESERVE_BYTES
|
MISSIONCORE_ARTIFACT_CACHE_FREE_SPACE_RESERVE_BYTES
|
||||||
```
|
```
|
||||||
|
|
||||||
The central root is configured with:
|
An optional shared root is configured explicitly with:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
MISSIONCORE_ARTIFACT_STORE_ROOT
|
MISSIONCORE_ARTIFACT_STORE_ROOT
|
||||||
@@ -115,16 +116,19 @@ When the central root is unavailable:
|
|||||||
`GET /api/health` exposes the effective shared/standalone mode, central-store
|
`GET /api/health` exposes the effective shared/standalone mode, central-store
|
||||||
availability, bounded CAS and RRD-cache occupancy, media-tool availability and
|
availability, bounded CAS and RRD-cache occupancy, media-tool availability and
|
||||||
the background catalog reconciler state without disclosing filesystem paths.
|
the background catalog reconciler state without disclosing filesystem paths.
|
||||||
Operational readiness remains distinct from production shared-artifact
|
`GET /api/health` reports local operational readiness independently. Optional
|
||||||
readiness so an offline, already pinned field host stays observable rather than
|
shared artifacts are exposed as a non-required capability with separate
|
||||||
being mistaken for a fully connected host.
|
`configured` and `ready` values. Their absence or temporary unavailability does
|
||||||
|
not degrade a healthy local contour. When an operator explicitly configures
|
||||||
|
that capability, its fail-closed cache-miss semantics still apply to artifacts
|
||||||
|
that were expected from the configured gateway.
|
||||||
|
|
||||||
## RAVNOVES00 migration acceptance
|
## RAVNOVES00 migration acceptance
|
||||||
|
|
||||||
The source session is `20260720T065719Z_viewer_live`, displayed as
|
The source session is `20260720T065719Z_viewer_live`, displayed as
|
||||||
`RAVNOVES00`.
|
`RAVNOVES00`.
|
||||||
|
|
||||||
The accepted central reference currently points to:
|
The reference used by that bounded migration acceptance pointed to:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
092f758c09965be98fd76d8191474c528ee20540dce5f4761c2a087f6056a6d5
|
092f758c09965be98fd76d8191474c528ee20540dce5f4761c2a087f6056a6d5
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Operator commands for the Mission Core central artifact gateway."""
|
"""Operator commands for the optional Mission Core artifact gateway."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Content-addressed central artifacts with a bounded, offline-capable host cache."""
|
"""Optional content-addressed artifact exchange with a bounded local cache."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|||||||
@@ -100,8 +100,9 @@ def build_runtime_readiness(
|
|||||||
"central_available": None,
|
"central_available": None,
|
||||||
}
|
}
|
||||||
shared_artifacts_ready = False
|
shared_artifacts_ready = False
|
||||||
shared_artifact_degraded = False
|
shared_artifacts_configured = False
|
||||||
else:
|
else:
|
||||||
|
shared_artifacts_configured = True
|
||||||
try:
|
try:
|
||||||
artifact = artifact_gateway.status()
|
artifact = artifact_gateway.status()
|
||||||
cache = artifact.cache
|
cache = artifact.cache
|
||||||
@@ -127,7 +128,6 @@ def build_runtime_readiness(
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
shared_artifacts_ready = artifact.central_status == "ready"
|
shared_artifacts_ready = artifact.central_status == "ready"
|
||||||
shared_artifact_degraded = not shared_artifacts_ready
|
|
||||||
except (OSError, RuntimeError):
|
except (OSError, RuntimeError):
|
||||||
artifact_document = {
|
artifact_document = {
|
||||||
"mode": "shared",
|
"mode": "shared",
|
||||||
@@ -135,7 +135,6 @@ def build_runtime_readiness(
|
|||||||
"central_available": False,
|
"central_available": False,
|
||||||
}
|
}
|
||||||
shared_artifacts_ready = False
|
shared_artifacts_ready = False
|
||||||
shared_artifact_degraded = True
|
|
||||||
|
|
||||||
media_tools_ready = ffmpeg_available and ffprobe_available
|
media_tools_ready = ffmpeg_available and ffprobe_available
|
||||||
reconciler_document = reconciler.snapshot()
|
reconciler_document = reconciler.snapshot()
|
||||||
@@ -151,11 +150,7 @@ def build_runtime_readiness(
|
|||||||
and media_tools_ready
|
and media_tools_ready
|
||||||
and reconciler_failures < 3
|
and reconciler_failures < 3
|
||||||
)
|
)
|
||||||
degraded = (
|
degraded = reconciler_failures > 0 or not operational_ready
|
||||||
shared_artifact_degraded
|
|
||||||
or reconciler_failures > 0
|
|
||||||
or not operational_ready
|
|
||||||
)
|
|
||||||
status = (
|
status = (
|
||||||
"unavailable"
|
"unavailable"
|
||||||
if not operational_ready
|
if not operational_ready
|
||||||
@@ -170,9 +165,11 @@ def build_runtime_readiness(
|
|||||||
"version": version,
|
"version": version,
|
||||||
"readiness": {
|
"readiness": {
|
||||||
"operational": operational_ready,
|
"operational": operational_ready,
|
||||||
"production_shared_artifacts": (
|
"optional_shared_artifacts": {
|
||||||
operational_ready and shared_artifacts_ready
|
"required": False,
|
||||||
),
|
"configured": shared_artifacts_configured,
|
||||||
|
"ready": shared_artifacts_ready,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"plugin_runtimes": {
|
"plugin_runtimes": {
|
||||||
"ready": ready_runtime_count,
|
"ready": ready_runtime_count,
|
||||||
|
|||||||
@@ -57,7 +57,11 @@ def test_runtime_readiness_reports_shared_store_and_bounded_cache(
|
|||||||
assert document["status"] == "ok"
|
assert document["status"] == "ok"
|
||||||
assert document["readiness"] == {
|
assert document["readiness"] == {
|
||||||
"operational": True,
|
"operational": True,
|
||||||
"production_shared_artifacts": True,
|
"optional_shared_artifacts": {
|
||||||
|
"required": False,
|
||||||
|
"configured": True,
|
||||||
|
"ready": True,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
components = document["components"]
|
components = document["components"]
|
||||||
assert isinstance(components, dict)
|
assert isinstance(components, dict)
|
||||||
@@ -85,16 +89,47 @@ def test_runtime_readiness_exposes_offline_cache_only_mode(
|
|||||||
document = _document(tmp_path, gateway=gateway, reconciler=reconciler)
|
document = _document(tmp_path, gateway=gateway, reconciler=reconciler)
|
||||||
|
|
||||||
assert document["ok"] is True
|
assert document["ok"] is True
|
||||||
assert document["status"] == "degraded"
|
assert document["status"] == "ok"
|
||||||
assert document["readiness"] == {
|
assert document["readiness"] == {
|
||||||
"operational": True,
|
"operational": True,
|
||||||
"production_shared_artifacts": False,
|
"optional_shared_artifacts": {
|
||||||
|
"required": False,
|
||||||
|
"configured": True,
|
||||||
|
"ready": False,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
components = document["components"]
|
components = document["components"]
|
||||||
assert isinstance(components, dict)
|
assert isinstance(components, dict)
|
||||||
assert components["artifact_store"]["status"] == "offline-cache-only"
|
assert components["artifact_store"]["status"] == "offline-cache-only"
|
||||||
|
|
||||||
|
|
||||||
|
def test_runtime_readiness_treats_standalone_as_complete_local_contour(
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
reconciler = BackgroundReconcilerReadiness()
|
||||||
|
reconciler.record_success()
|
||||||
|
|
||||||
|
document = _document(tmp_path, gateway=None, reconciler=reconciler)
|
||||||
|
|
||||||
|
assert document["ok"] is True
|
||||||
|
assert document["status"] == "ok"
|
||||||
|
assert document["readiness"] == {
|
||||||
|
"operational": True,
|
||||||
|
"optional_shared_artifacts": {
|
||||||
|
"required": False,
|
||||||
|
"configured": False,
|
||||||
|
"ready": False,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
components = document["components"]
|
||||||
|
assert isinstance(components, dict)
|
||||||
|
assert components["artifact_store"] == {
|
||||||
|
"mode": "standalone",
|
||||||
|
"status": "not-configured",
|
||||||
|
"central_available": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_runtime_readiness_fails_after_repeated_reconciler_errors(
|
def test_runtime_readiness_fails_after_repeated_reconciler_errors(
|
||||||
tmp_path: Path,
|
tmp_path: Path,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user