generated from coulomb/repo-seed
Wire ops inventory probes for Railiance
This commit is contained in:
@@ -15,6 +15,9 @@ data:
|
||||
ISSUE_CORE_URL: http://issue-core.issue-core.svc.cluster.local:8010
|
||||
ISSUE_SINK_TYPE: "null"
|
||||
ACTIVITY_DEFINITION_DIRS: /etc/activity-core/external-definitions
|
||||
OPS_INVENTORY_PATH: /etc/activity-core/ops/service-inventory.yml
|
||||
INTER_HUB_URL: ""
|
||||
OPS_HUB_WIDGET_MAPPING: ""
|
||||
PROMETHEUS_BIND_ADDR: 0.0.0.0:9090
|
||||
ACTIVITY_CURATOR_GATE: disabled
|
||||
---
|
||||
@@ -58,6 +61,219 @@ data:
|
||||
|
||||
Kubernetes projection of the Custodian-owned definition in
|
||||
`/home/worsch/the-custodian/activity-definitions/hourly-recently-on-scope.md`.
|
||||
ops-service-inventory-probes.md: |
|
||||
---
|
||||
id: "40d15a87-7ff6-4d8e-992c-37df15f95110"
|
||||
name: "Ops Service Inventory Probes"
|
||||
type: activity-definition
|
||||
version: "0.1"
|
||||
enabled: false
|
||||
owner: custodian
|
||||
governance: custodian
|
||||
status: proposed
|
||||
created: "2026-06-05"
|
||||
trigger:
|
||||
type: cron
|
||||
cron_expression: "15 * * * *"
|
||||
timezone: Europe/Berlin
|
||||
misfire_policy: skip
|
||||
context_sources:
|
||||
- type: ops-inventory
|
||||
query: probe_services
|
||||
required: false
|
||||
params:
|
||||
inventory_path: /etc/activity-core/ops/service-inventory.yml
|
||||
timeout_seconds: 10
|
||||
include_kinds:
|
||||
- http
|
||||
- https
|
||||
allow_network: true
|
||||
evidence_sinks:
|
||||
- type: state-hub-progress
|
||||
event_type: ops_inventory_probe
|
||||
author: activity-core
|
||||
bind_to: context.ops_inventory_probe
|
||||
---
|
||||
|
||||
# ActivityDefinition: Ops Service Inventory Probes
|
||||
|
||||
Disabled Railiance projection of the Custodian-owned definition in
|
||||
`/home/worsch/the-custodian/activity-definitions/ops-service-inventory-probes.md`.
|
||||
Keep disabled until ops-hub Inter-Hub evidence intake is active.
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: actcore-ops-service-inventory
|
||||
namespace: activity-core
|
||||
labels:
|
||||
app.kubernetes.io/name: activity-core
|
||||
app.kubernetes.io/part-of: activity-core
|
||||
data:
|
||||
service-inventory.yml: |
|
||||
version: 1
|
||||
last_reviewed: "2026-06-05"
|
||||
policy:
|
||||
non_secret_inventory: true
|
||||
source_of_truth: "/home/worsch/the-custodian/ops/service-inventory.yml"
|
||||
projection: "Railiance activity-core ConfigMap snapshot for disabled probes"
|
||||
environments:
|
||||
- id: local
|
||||
name: "Local Workstation"
|
||||
role: "Workstation development and local operations"
|
||||
lifecycle_state: observed
|
||||
- id: coulombcore
|
||||
name: "CoulombCore"
|
||||
role: "Transitional production-like runtime"
|
||||
lifecycle_state: observed
|
||||
- id: railiance01
|
||||
name: "Railiance01"
|
||||
role: "First ThreePhoenix foundation node"
|
||||
lifecycle_state: observed
|
||||
- id: threephoenix-prod
|
||||
name: "ThreePhoenix Production"
|
||||
role: "Target governed production topology"
|
||||
lifecycle_state: planned
|
||||
hosts:
|
||||
- id: local-workstation
|
||||
environment: local
|
||||
role: "State Hub and operator workstation runtime"
|
||||
- id: coulombcore
|
||||
environment: coulombcore
|
||||
address: "92.205.130.254"
|
||||
role: "Current live production-like server"
|
||||
- id: railiance01
|
||||
environment: railiance01
|
||||
address: "92.205.62.239"
|
||||
role: "First ThreePhoenix foundation node"
|
||||
clusters:
|
||||
- id: coulombcore-k3s
|
||||
environment: coulombcore
|
||||
host: coulombcore
|
||||
kind: k3s
|
||||
lifecycle_state: observed
|
||||
- id: railiance01-k3s
|
||||
environment: railiance01
|
||||
host: railiance01
|
||||
kind: k3s
|
||||
lifecycle_state: observed
|
||||
services:
|
||||
- id: gitea
|
||||
name: "Gitea"
|
||||
kind: application
|
||||
lifecycle_state: observed
|
||||
health_status: unknown
|
||||
environment: coulombcore
|
||||
owner_repos:
|
||||
- railiance-apps
|
||||
runtime:
|
||||
type: k3s
|
||||
cluster: coulombcore-k3s
|
||||
namespace: default
|
||||
endpoints:
|
||||
- id: gitea-oci-registry
|
||||
type: https
|
||||
url: "https://gitea.coulomb.social/v2/"
|
||||
expected_status: 401
|
||||
expected_signal: "OCI registry auth challenge"
|
||||
widget_ref: "ops:endpoint:gitea-registry"
|
||||
backing_stores:
|
||||
- "database:gitea-db"
|
||||
- "pvc:default/gitea-shared-storage"
|
||||
access_paths:
|
||||
- type: k8s
|
||||
target: "coulombcore-k3s/default"
|
||||
status: unknown
|
||||
evidence: []
|
||||
gaps:
|
||||
- "Backup and restore evidence for database and shared storage not recorded in ops inventory."
|
||||
- id: state-hub
|
||||
name: "State Hub"
|
||||
kind: coordination-service
|
||||
lifecycle_state: observed
|
||||
health_status: observed_ok
|
||||
environment: local
|
||||
owner_repos:
|
||||
- state-hub
|
||||
- the-custodian
|
||||
runtime:
|
||||
type: local-process
|
||||
host: local-workstation
|
||||
endpoints:
|
||||
- id: state-hub-local-api
|
||||
type: http
|
||||
url: "http://actcore-state-hub-bridge:8000/state/health"
|
||||
expected_status: 200
|
||||
expected_signal: "health response"
|
||||
backing_stores:
|
||||
- "postgresql:state-hub"
|
||||
access_paths:
|
||||
- type: http
|
||||
target: "http://actcore-state-hub-bridge:8000"
|
||||
status: observed_ok
|
||||
evidence: []
|
||||
gaps:
|
||||
- "Future cluster deployment readiness still needs ops evidence."
|
||||
- id: inter-hub
|
||||
name: "Inter-Hub"
|
||||
kind: governance-service
|
||||
lifecycle_state: observed
|
||||
health_status: unknown
|
||||
environment: threephoenix-prod
|
||||
owner_repos:
|
||||
- inter-hub
|
||||
runtime:
|
||||
type: external
|
||||
public_endpoint: "https://hub.coulomb.social"
|
||||
endpoints:
|
||||
- id: inter-hub-openapi
|
||||
type: https
|
||||
url: "https://hub.coulomb.social/api/v2/openapi.json"
|
||||
expected_status: 200
|
||||
expected_signal: "OpenAPI document"
|
||||
- id: inter-hub-ui
|
||||
type: https
|
||||
url: "https://hub.coulomb.social/Hubs"
|
||||
expected_status: 302
|
||||
expected_signal: "login redirect when unauthenticated"
|
||||
backing_stores: []
|
||||
access_paths:
|
||||
- type: https
|
||||
target: "https://hub.coulomb.social"
|
||||
status: unknown
|
||||
evidence: []
|
||||
gaps:
|
||||
- "ops-hub bootstrap requires authenticated UI flow or deployment-side migration."
|
||||
- id: activity-core
|
||||
name: "activity-core"
|
||||
kind: automation-service
|
||||
lifecycle_state: observed
|
||||
health_status: observed_ok
|
||||
environment: railiance01
|
||||
owner_repos:
|
||||
- activity-core
|
||||
- the-custodian
|
||||
runtime:
|
||||
type: k3s
|
||||
cluster: railiance01-k3s
|
||||
namespace: activity-core
|
||||
endpoints:
|
||||
- id: activity-core-api
|
||||
type: cluster-http
|
||||
url: "http://actcore-api:8010/health"
|
||||
expected_status: 200
|
||||
expected_signal: "db"
|
||||
backing_stores:
|
||||
- "postgresql:activity-core"
|
||||
- "temporal:activity-core"
|
||||
- "nats:railiance01"
|
||||
access_paths:
|
||||
- type: k8s
|
||||
target: "railiance01-k3s/activity-core"
|
||||
status: observed_ok
|
||||
evidence: []
|
||||
gaps:
|
||||
- "Add explicit ops inventory probes and evidence events."
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
@@ -360,10 +576,16 @@ spec:
|
||||
- name: external-activity-definitions
|
||||
mountPath: /etc/activity-core/external-definitions/activity-definitions
|
||||
readOnly: true
|
||||
- name: ops-service-inventory
|
||||
mountPath: /etc/activity-core/ops
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: external-activity-definitions
|
||||
configMap:
|
||||
name: actcore-external-activity-definitions
|
||||
- name: ops-service-inventory
|
||||
configMap:
|
||||
name: actcore-ops-service-inventory
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
|
||||
@@ -16,6 +16,14 @@ name and access policy.
|
||||
The runtime image tag is `activity-core:railiance01-prod` and is expected to be
|
||||
loaded into the railiance01 K3s containerd image store.
|
||||
|
||||
`20-runtime.yaml` also projects the disabled Custodian-owned
|
||||
`ops-service-inventory-probes.md` ActivityDefinition and a non-secret
|
||||
`actcore-ops-service-inventory` ConfigMap snapshot. The source of truth for the
|
||||
inventory remains `/home/worsch/the-custodian/ops/service-inventory.yml`; update
|
||||
the ConfigMap projection from that file before enabling the probe schedule.
|
||||
`OPS_HUB_KEY` is created only as an empty Secret placeholder until the operator
|
||||
provisions the Inter-Hub ops-hub key.
|
||||
|
||||
## Deploy
|
||||
|
||||
```bash
|
||||
|
||||
@@ -36,5 +36,6 @@ if ! secret_exists actcore-runtime-secret; then
|
||||
kubectl -n "$NS" create secret generic actcore-runtime-secret \
|
||||
--from-literal=ACTCORE_DB_URL="$ACTCORE_DB_URL" \
|
||||
--from-literal=WEBHOOK_SECRET_GITEA="" \
|
||||
--from-literal=WEBHOOK_SECRET_GITHUB=""
|
||||
--from-literal=WEBHOOK_SECRET_GITHUB="" \
|
||||
--from-literal=OPS_HUB_KEY=""
|
||||
fi
|
||||
|
||||
@@ -158,7 +158,11 @@ def _inter_hub_result(sink: dict[str, Any]) -> dict[str, Any]:
|
||||
missing.append("INTER_HUB_URL")
|
||||
if not os.environ.get("OPS_HUB_KEY"):
|
||||
missing.append("OPS_HUB_KEY")
|
||||
if not (sink.get("widget_mapping") or sink.get("capability_mapping")):
|
||||
if not (
|
||||
sink.get("widget_mapping")
|
||||
or sink.get("capability_mapping")
|
||||
or os.environ.get("OPS_HUB_WIDGET_MAPPING")
|
||||
):
|
||||
missing.append("widget_mapping")
|
||||
|
||||
if missing:
|
||||
|
||||
@@ -184,6 +184,25 @@ def test_inter_hub_sink_skips_cleanly_when_config_missing(monkeypatch) -> None:
|
||||
]
|
||||
|
||||
|
||||
def test_inter_hub_sink_accepts_widget_mapping_from_env(monkeypatch) -> None:
|
||||
monkeypatch.delenv("INTER_HUB_URL", raising=False)
|
||||
monkeypatch.delenv("OPS_HUB_KEY", raising=False)
|
||||
monkeypatch.setenv("OPS_HUB_WIDGET_MAPPING", "ops:endpoint:gitea-registry")
|
||||
|
||||
result = persist_ops_inventory_evidence(
|
||||
_payload([{"type": "inter-hub-interaction-event"}])
|
||||
)
|
||||
|
||||
assert result == [
|
||||
{
|
||||
"type": "inter-hub-interaction-event",
|
||||
"status": "skipped",
|
||||
"reason": "missing_inter_hub_config",
|
||||
"missing": ["INTER_HUB_URL", "OPS_HUB_KEY"],
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def test_no_evidence_sinks_returns_no_results() -> None:
|
||||
payload = _payload([])
|
||||
payload["context_sources"][0]["params"] = {}
|
||||
|
||||
192
tests/test_railiance_ops_inventory_wiring.py
Normal file
192
tests/test_railiance_ops_inventory_wiring.py
Normal file
@@ -0,0 +1,192 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import yaml
|
||||
import httpx
|
||||
|
||||
from activity_core.definition_parser import parse_file
|
||||
from activity_core.context_resolvers.ops_inventory import OpsInventoryContextResolver
|
||||
from activity_core.ops_evidence_sinks import persist_ops_inventory_evidence
|
||||
|
||||
_REPO_ROOT = Path(__file__).parent.parent
|
||||
_RUNTIME_PATH = _REPO_ROOT / "k8s" / "railiance" / "20-runtime.yaml"
|
||||
_BOOTSTRAP_SECRETS_PATH = _REPO_ROOT / "k8s" / "railiance" / "bootstrap-secrets.sh"
|
||||
|
||||
|
||||
def _resources() -> list[dict[str, Any]]:
|
||||
return [
|
||||
resource
|
||||
for resource in yaml.safe_load_all(_RUNTIME_PATH.read_text(encoding="utf-8"))
|
||||
if isinstance(resource, dict)
|
||||
]
|
||||
|
||||
|
||||
def _by_kind_name(kind: str, name: str) -> dict[str, Any]:
|
||||
for resource in _resources():
|
||||
if resource.get("kind") == kind and resource.get("metadata", {}).get("name") == name:
|
||||
return resource
|
||||
raise AssertionError(f"missing {kind}/{name}")
|
||||
|
||||
|
||||
def test_runtime_config_has_ops_inventory_placeholders() -> None:
|
||||
config = _by_kind_name("ConfigMap", "actcore-runtime-config")
|
||||
|
||||
assert config["data"]["OPS_INVENTORY_PATH"] == (
|
||||
"/etc/activity-core/ops/service-inventory.yml"
|
||||
)
|
||||
assert config["data"]["INTER_HUB_URL"] == ""
|
||||
assert config["data"]["OPS_HUB_WIDGET_MAPPING"] == ""
|
||||
|
||||
|
||||
def test_external_configmap_projects_disabled_ops_probe_definition(tmp_path) -> None:
|
||||
config = _by_kind_name("ConfigMap", "actcore-external-activity-definitions")
|
||||
raw_definition = config["data"]["ops-service-inventory-probes.md"]
|
||||
definition_path = tmp_path / "ops-service-inventory-probes.md"
|
||||
definition_path.write_text(raw_definition, encoding="utf-8")
|
||||
|
||||
definition = parse_file(definition_path)
|
||||
|
||||
assert definition.name == "Ops Service Inventory Probes"
|
||||
assert definition.enabled is False
|
||||
assert definition.trigger_config["cron_expression"] == "15 * * * *"
|
||||
assert definition.context_sources == [
|
||||
{
|
||||
"type": "ops-inventory",
|
||||
"query": "probe_services",
|
||||
"required": False,
|
||||
"params": {
|
||||
"inventory_path": "/etc/activity-core/ops/service-inventory.yml",
|
||||
"timeout_seconds": 10,
|
||||
"include_kinds": ["http", "https"],
|
||||
"allow_network": True,
|
||||
"evidence_sinks": [
|
||||
{
|
||||
"type": "state-hub-progress",
|
||||
"event_type": "ops_inventory_probe",
|
||||
"author": "activity-core",
|
||||
}
|
||||
],
|
||||
},
|
||||
"bind_to": "context.ops_inventory_probe",
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def test_ops_inventory_configmap_contains_probeable_inventory() -> None:
|
||||
config = _by_kind_name("ConfigMap", "actcore-ops-service-inventory")
|
||||
inventory = yaml.safe_load(config["data"]["service-inventory.yml"])
|
||||
|
||||
services = {service["id"]: service for service in inventory["services"]}
|
||||
|
||||
assert inventory["policy"]["non_secret_inventory"] is True
|
||||
assert services["gitea"]["endpoints"][0]["id"] == "gitea-oci-registry"
|
||||
assert services["state-hub"]["endpoints"][0]["url"] == (
|
||||
"http://actcore-state-hub-bridge:8000/state/health"
|
||||
)
|
||||
assert services["inter-hub"]["endpoints"][0]["id"] == "inter-hub-openapi"
|
||||
assert services["activity-core"]["endpoints"][0]["id"] == "activity-core-api"
|
||||
|
||||
|
||||
def test_worker_mounts_ops_inventory_configmap() -> None:
|
||||
deployment = _by_kind_name("Deployment", "actcore-worker")
|
||||
pod_spec = deployment["spec"]["template"]["spec"]
|
||||
container = pod_spec["containers"][0]
|
||||
|
||||
mounts = {mount["name"]: mount for mount in container["volumeMounts"]}
|
||||
volumes = {volume["name"]: volume for volume in pod_spec["volumes"]}
|
||||
|
||||
assert mounts["ops-service-inventory"]["mountPath"] == "/etc/activity-core/ops"
|
||||
assert mounts["ops-service-inventory"]["readOnly"] is True
|
||||
assert volumes["ops-service-inventory"]["configMap"]["name"] == (
|
||||
"actcore-ops-service-inventory"
|
||||
)
|
||||
|
||||
|
||||
def test_ops_hub_key_is_secret_only_placeholder() -> None:
|
||||
runtime_config = _by_kind_name("ConfigMap", "actcore-runtime-config")
|
||||
bootstrap = _BOOTSTRAP_SECRETS_PATH.read_text(encoding="utf-8")
|
||||
|
||||
assert "OPS_HUB_KEY" not in runtime_config["data"]
|
||||
assert '--from-literal=OPS_HUB_KEY=""' in bootstrap
|
||||
|
||||
|
||||
def test_disabled_ops_probe_definition_can_emit_fixture_evidence(
|
||||
tmp_path,
|
||||
monkeypatch,
|
||||
) -> None:
|
||||
definition_config = _by_kind_name("ConfigMap", "actcore-external-activity-definitions")
|
||||
inventory_config = _by_kind_name("ConfigMap", "actcore-ops-service-inventory")
|
||||
definition_path = tmp_path / "ops-service-inventory-probes.md"
|
||||
inventory_path = tmp_path / "service-inventory.yml"
|
||||
definition_path.write_text(
|
||||
definition_config["data"]["ops-service-inventory-probes.md"],
|
||||
encoding="utf-8",
|
||||
)
|
||||
inventory_path.write_text(
|
||||
inventory_config["data"]["service-inventory.yml"],
|
||||
encoding="utf-8",
|
||||
)
|
||||
definition = parse_file(definition_path)
|
||||
source = definition.context_sources[0]
|
||||
source["params"]["inventory_path"] = str(inventory_path)
|
||||
|
||||
def fake_endpoint_get(url: str, **kwargs: Any) -> Any:
|
||||
if url.endswith("/v2/"):
|
||||
return _HttpResponse(401, "OCI registry auth challenge")
|
||||
if url.endswith("/state/health"):
|
||||
return _HttpResponse(200, "health response")
|
||||
if url.endswith("/openapi.json"):
|
||||
return _HttpResponse(200, "OpenAPI document")
|
||||
if url.endswith("/Hubs"):
|
||||
return _HttpResponse(302, "login redirect when unauthenticated")
|
||||
raise AssertionError(f"unexpected endpoint probe {url}")
|
||||
|
||||
monkeypatch.setattr(httpx, "get", fake_endpoint_get)
|
||||
probe = OpsInventoryContextResolver().resolve("probe_services", None, source["params"])
|
||||
|
||||
posts: list[dict[str, Any]] = []
|
||||
|
||||
def fake_progress_get(url: str, **kwargs: Any) -> _JsonResponse:
|
||||
return _JsonResponse([])
|
||||
|
||||
def fake_progress_post(url: str, **kwargs: Any) -> _JsonResponse:
|
||||
posts.append({"url": url, **kwargs})
|
||||
return _JsonResponse({"id": "progress-1"})
|
||||
|
||||
monkeypatch.setattr(httpx, "get", fake_progress_get)
|
||||
monkeypatch.setattr(httpx, "post", fake_progress_post)
|
||||
|
||||
result = persist_ops_inventory_evidence(
|
||||
{
|
||||
"activity_id": definition.id,
|
||||
"run_id": "12345678-aaaa-bbbb-cccc-123456789abc",
|
||||
"scheduled_for": "2026-06-05T10:15:00+00:00",
|
||||
"version_used": 1,
|
||||
"context_sources": [source],
|
||||
"context": {"ops_inventory_probe": probe},
|
||||
}
|
||||
)
|
||||
|
||||
assert definition.enabled is False
|
||||
assert result[0]["status"] == "posted"
|
||||
assert posts[0]["json"]["event_type"] == "ops_inventory_probe"
|
||||
assert posts[0]["json"]["detail"]["probe"]["summary"]["ok"] == 4
|
||||
|
||||
|
||||
class _HttpResponse:
|
||||
def __init__(self, status_code: int, text: str) -> None:
|
||||
self.status_code = status_code
|
||||
self.text = text
|
||||
|
||||
|
||||
class _JsonResponse:
|
||||
def __init__(self, payload: Any) -> None:
|
||||
self.payload = payload
|
||||
|
||||
def raise_for_status(self) -> None:
|
||||
return None
|
||||
|
||||
def json(self) -> Any:
|
||||
return self.payload
|
||||
@@ -159,7 +159,7 @@ valid and reviewable.
|
||||
|
||||
```task
|
||||
id: ACTIVITY-WP-0007-T04
|
||||
status: progress
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "45132f9f-da3c-44f1-a488-195aa0e46428"
|
||||
```
|
||||
@@ -184,11 +184,17 @@ it prematurely.
|
||||
scan a disabled `ops-service-inventory-probes.md` definition carrying an
|
||||
`ops-inventory` context source and explicit `state-hub-progress` evidence sink.
|
||||
|
||||
2026-06-05: Completed. The Railiance-projected disabled definition now uses the
|
||||
`ops-inventory` resolver and explicit `state-hub-progress` evidence sink. Tests
|
||||
prove the disabled definition can resolve fixture inventory data and emit one
|
||||
compact `ops_inventory_probe` State Hub progress event without enabling the
|
||||
production schedule.
|
||||
|
||||
## Wire Railiance Runtime Inputs
|
||||
|
||||
```task
|
||||
id: ACTIVITY-WP-0007-T05
|
||||
status: todo
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "474564be-a447-4bdf-b995-168f7a93e515"
|
||||
```
|
||||
@@ -209,6 +215,13 @@ Scope:
|
||||
Done when the Railiance worker can see the disabled definition and inventory
|
||||
input without leaking secrets or activating the schedule early.
|
||||
|
||||
2026-06-05: Completed the first production wiring slice. `20-runtime.yaml`
|
||||
projects the disabled ops probe definition, runtime config placeholders
|
||||
(`OPS_INVENTORY_PATH`, `INTER_HUB_URL`, `OPS_HUB_WIDGET_MAPPING`), and a
|
||||
non-secret `actcore-ops-service-inventory` ConfigMap snapshot. The worker mounts
|
||||
the inventory at `/etc/activity-core/ops`, and `bootstrap-secrets.sh` keeps
|
||||
`OPS_HUB_KEY` as an empty Secret-only placeholder until operator provisioning.
|
||||
|
||||
## Close Safety And Handoff Gates
|
||||
|
||||
```task
|
||||
@@ -235,6 +248,10 @@ Acceptance criteria:
|
||||
This task waits on the implementation tasks above and, for final Inter-Hub
|
||||
activation, the operator-gated ops-hub widget/API-key path in `CUST-WP-0047`.
|
||||
|
||||
2026-06-05: The local implementation gates are now satisfied and tested. Live
|
||||
closure remains waiting on applying the updated Railiance manifests and on the
|
||||
operator-gated Inter-Hub ops-hub widget/API-key path.
|
||||
|
||||
## Review Verdict
|
||||
|
||||
activity-core should provide this as a bounded probe-and-evidence capability.
|
||||
|
||||
Reference in New Issue
Block a user