diff --git a/k8s/railiance/20-runtime.yaml b/k8s/railiance/20-runtime.yaml index 091f428..87dbcc3 100644 --- a/k8s/railiance/20-runtime.yaml +++ b/k8s/railiance/20-runtime.yaml @@ -338,7 +338,7 @@ data: last_reviewed: "2026-06-05" policy: non_secret_inventory: true - source_of_truth: "/home/worsch/the-custodian/ops/service-inventory.yml" + source_of_truth: "custodian://ops/service-inventory.yml" projection: "Railiance activity-core ConfigMap snapshot for disabled probes" environments: - id: local @@ -1012,6 +1012,10 @@ spec: app.kubernetes.io/name: actcore-worker app.kubernetes.io/part-of: activity-core spec: + securityContext: + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 containers: - name: worker image: activity-core:railiance01-prod @@ -1051,8 +1055,9 @@ spec: configMap: name: actcore-ops-service-inventory - name: working-memory - persistentVolumeClaim: - claimName: actcore-working-memory + hostPath: + path: /home/tegwick/the-custodian/memory/working + type: DirectoryOrCreate - name: custodian-runtime configMap: name: actcore-custodian-runtime diff --git a/k8s/railiance/README.md b/k8s/railiance/README.md index 1ff3df0..e1d12ba 100644 --- a/k8s/railiance/README.md +++ b/k8s/railiance/README.md @@ -19,7 +19,7 @@ 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 +inventory source of truth remains `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. @@ -27,7 +27,8 @@ provisions the Inter-Hub ops-hub key. The same runtime projection now includes the active `daily-statehub-wsjf-triage.md` ActivityDefinition plus its JSON output schema and a persistent working-memory volume mounted at -`/home/worsch/the-custodian/memory/working`. Before trusting the daily 07:20 +`/var/custodian/memory/working` (hostPath → `/home/tegwick/the-custodian/memory/working`). +Before trusting the daily 07:20 Europe/Berlin schedule, verify both runtime dependencies: - `actcore-state-hub-bridge` can reach the State Hub API through the node-local diff --git a/scripts/verify_daily_triage.py b/scripts/verify_daily_triage.py index df27743..8a8f56d 100644 --- a/scripts/verify_daily_triage.py +++ b/scripts/verify_daily_triage.py @@ -24,7 +24,7 @@ DEFAULT_PROGRESS_EVENT_TYPE = "daily_triage" DEFAULT_TEMPORAL_HOST = "localhost:7233" DEFAULT_TEMPORAL_NAMESPACE = "default" DEFAULT_STATE_HUB_URL = "http://127.0.0.1:8000" -DEFAULT_WORKING_MEMORY_DIR = "/home/worsch/the-custodian/memory/working" +from activity_core.runtime_paths import custodian_working_memory_dir def parse_args(argv: list[str] | None = None) -> argparse.Namespace: @@ -50,10 +50,13 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace: DEFAULT_STATE_HUB_URL, )) parser.add_argument("--progress-event-type", default=DEFAULT_PROGRESS_EVENT_TYPE) - parser.add_argument("--working-memory-dir", default=os.environ.get( - "DAILY_TRIAGE_WORKING_MEMORY_DIR", - DEFAULT_WORKING_MEMORY_DIR, - )) + parser.add_argument( + "--working-memory-dir", + default=os.environ.get( + "DAILY_TRIAGE_WORKING_MEMORY_DIR", + str(custodian_working_memory_dir()), + ), + ) parser.add_argument( "--date", default=datetime.now(timezone.utc).date().isoformat(), diff --git a/src/activity_core/automation_status.py b/src/activity_core/automation_status.py index 9462a31..427a167 100644 --- a/src/activity_core/automation_status.py +++ b/src/activity_core/automation_status.py @@ -24,7 +24,7 @@ from activity_core.sync_activity_definitions import ACTIVITY_DEFINITION_ID_NAMES DEFAULT_TIMEZONE = "Europe/Berlin" DEFAULT_STATE_HUB_URL = "http://127.0.0.1:8000" -DEFAULT_WORKING_MEMORY_DIR = "/home/worsch/the-custodian/memory/working" +from activity_core.runtime_paths import custodian_working_memory_dir DEFAULT_TEMPORAL_NAMESPACE = "default" FAILURE_STATUSES = {"missed", "validation_failed", "sink_failed"} WEEKDAYS = { @@ -57,7 +57,13 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace: parser.add_argument("--activity-name", action="append", default=[]) parser.add_argument("--db-url", default=os.environ.get("ACTCORE_DB_URL")) parser.add_argument("--state-hub-url", default=os.environ.get("STATE_HUB_URL", DEFAULT_STATE_HUB_URL)) - parser.add_argument("--working-memory-dir", default=os.environ.get("AUTOMATION_STATUS_WORKING_MEMORY_DIR", DEFAULT_WORKING_MEMORY_DIR)) + parser.add_argument( + "--working-memory-dir", + default=os.environ.get( + "AUTOMATION_STATUS_WORKING_MEMORY_DIR", + str(custodian_working_memory_dir()), + ), + ) parser.add_argument("--temporal-host", default=os.environ.get("TEMPORAL_HOST")) parser.add_argument("--temporal-namespace", default=os.environ.get("TEMPORAL_NAMESPACE", DEFAULT_TEMPORAL_NAMESPACE)) parser.add_argument("--timeout-seconds", type=float, default=float(os.environ.get("AUTOMATION_STATUS_TIMEOUT_SECONDS", "5"))) diff --git a/src/activity_core/context_resolvers/ops_inventory.py b/src/activity_core/context_resolvers/ops_inventory.py index d763c69..5463746 100644 --- a/src/activity_core/context_resolvers/ops_inventory.py +++ b/src/activity_core/context_resolvers/ops_inventory.py @@ -20,8 +20,7 @@ import httpx import yaml from activity_core.context_resolvers.base import CONTEXT_RESOLVER_REGISTRY, ContextResolver - -_DEFAULT_INVENTORY_PATH = "/home/worsch/the-custodian/ops/service-inventory.yml" +from activity_core.runtime_paths import default_ops_inventory_path _DEFAULT_TIMEOUT_SECONDS = 10.0 _SUPPORTED_ENDPOINT_TYPES = {"http", "https"} @@ -39,12 +38,11 @@ CONTEXT_RESOLVER_REGISTRY["ops-inventory"] = OpsInventoryContextResolver def _probe_services(params: dict[str, Any]) -> dict[str, Any]: - inventory_path = Path( - str( - params.get("inventory_path") - or os.environ.get("OPS_INVENTORY_PATH") - or _DEFAULT_INVENTORY_PATH - ) + inventory_raw = params.get("inventory_path") + inventory_path = ( + default_ops_inventory_path() + if not inventory_raw + else Path(str(inventory_raw)).expanduser() ) timeout_seconds = float(params.get("timeout_seconds", _DEFAULT_TIMEOUT_SECONDS)) allow_network = _bool_param(params.get("allow_network", True)) diff --git a/src/activity_core/report_sinks.py b/src/activity_core/report_sinks.py index 2c01e70..9b64c52 100644 --- a/src/activity_core/report_sinks.py +++ b/src/activity_core/report_sinks.py @@ -11,7 +11,11 @@ from zoneinfo import ZoneInfo import httpx -from activity_core.runtime_paths import custodian_repo_root, resolve_runtime_path +from activity_core.runtime_paths import ( + custodian_repo_relative, + custodian_repo_root, + resolve_runtime_path, +) from activity_core.state_hub_write import idempotency_headers _DEFAULT_STATE_HUB_URL = "http://127.0.0.1:8000" @@ -82,13 +86,14 @@ def _write_working_memory( target = (directory / filename).resolve() _assert_allowed_output_path(target) + repo_relative = custodian_repo_relative(target) if target.exists(): text = target.read_text(encoding="utf-8") if f"activity_core_run_id: {run_id}" in text: return { "type": "working-memory", "status": "exists", - "path": str(target), + "path": repo_relative, } raise FileExistsError(f"refusing to overwrite existing report note: {target}") @@ -97,7 +102,7 @@ def _write_working_memory( return { "type": "working-memory", "status": "written", - "path": str(target), + "path": repo_relative, } diff --git a/src/activity_core/runtime_paths.py b/src/activity_core/runtime_paths.py index 7e7a03f..dfa9b9e 100644 --- a/src/activity_core/runtime_paths.py +++ b/src/activity_core/runtime_paths.py @@ -34,4 +34,24 @@ def resolve_runtime_path(raw_path: str) -> Path: if value.startswith(_ACTIVITY_CORE_SCHEME): return (activity_core_root() / value.removeprefix(_ACTIVITY_CORE_SCHEME)).resolve() - return Path(value).expanduser() \ No newline at end of file + return Path(value).expanduser() + + +def custodian_working_memory_dir() -> Path: + return resolve_runtime_path("custodian://memory/working") + + +def custodian_repo_relative(path: Path) -> str: + """Return a repo-relative POSIX path when under CUSTODIAN_REPO_ROOT.""" + root = custodian_repo_root().resolve() + try: + return path.resolve().relative_to(root).as_posix() + except ValueError: + return path.as_posix() + + +def default_ops_inventory_path() -> Path: + raw = os.environ.get("OPS_INVENTORY_PATH", "").strip() + if raw: + return resolve_runtime_path(raw) + return resolve_runtime_path("custodian://ops/service-inventory.yml") \ No newline at end of file diff --git a/tests/test_railiance_ops_inventory_wiring.py b/tests/test_railiance_ops_inventory_wiring.py index ca64244..caec4c9 100644 --- a/tests/test_railiance_ops_inventory_wiring.py +++ b/tests/test_railiance_ops_inventory_wiring.py @@ -152,12 +152,11 @@ def test_worker_mounts_daily_triage_schema_and_working_memory() -> None: assert mounts["report-schemas"]["mountPath"] == "/etc/activity-core/schemas" assert mounts["report-schemas"]["readOnly"] is True assert volumes["report-schemas"]["configMap"]["name"] == "actcore-report-schemas" - assert mounts["working-memory"]["mountPath"] == ( - "/home/worsch/the-custodian/memory/working" - ) - assert volumes["working-memory"]["persistentVolumeClaim"]["claimName"] == ( - "actcore-working-memory" + assert mounts["working-memory"]["mountPath"] == "/var/custodian/memory/working" + assert volumes["working-memory"]["hostPath"]["path"] == ( + "/home/tegwick/the-custodian/memory/working" ) + assert pod_spec["securityContext"]["runAsUser"] == 1000 def test_ops_hub_key_is_secret_only_placeholder() -> None: diff --git a/tests/test_report_sinks.py b/tests/test_report_sinks.py index f020ae3..5c882c9 100644 --- a/tests/test_report_sinks.py +++ b/tests/test_report_sinks.py @@ -61,6 +61,7 @@ def test_working_memory_sink_writes_idempotently(tmp_path) -> None: assert first[0]["status"] == "written" assert second[0]["status"] == "exists" note = tmp_path / "daily-triage-2026-05-19-12345678.md" + assert first[0]["path"].endswith("daily-triage-2026-05-19-12345678.md") text = note.read_text(encoding="utf-8") assert "activity_core_run_id: 12345678-aaaa-bbbb-cccc-123456789abc" in text assert "output_validated: true" in text diff --git a/tests/test_runtime_paths.py b/tests/test_runtime_paths.py index 8c4c731..bec71a8 100644 --- a/tests/test_runtime_paths.py +++ b/tests/test_runtime_paths.py @@ -14,4 +14,12 @@ def test_resolve_activity_core_uri(monkeypatch) -> None: monkeypatch.setenv("ACTIVITY_CORE_ROOT", "/etc/activity-core") assert resolve_runtime_path("activity-core://schemas/daily-triage-report.json") == ( __import__("pathlib").Path("/etc/activity-core/schemas/daily-triage-report.json") - ) \ No newline at end of file + ) + + +def test_custodian_repo_relative(monkeypatch) -> None: + from activity_core.runtime_paths import custodian_repo_relative + + monkeypatch.setenv("CUSTODIAN_REPO_ROOT", "/var/custodian") + path = __import__("pathlib").Path("/var/custodian/memory/working/note.md") + assert custodian_repo_relative(path) == "memory/working/note.md" \ No newline at end of file