apiVersion: v1 data: ACTIVITY_CORE_ROOT: /etc/activity-core ACTIVITY_CORE_WORKERS: rein-aharness@railiance01=ACTIVITY_CORE_WORKER_TOKEN,rein-aharness-metered@railiance01=ACTIVITY_CORE_WORKER_TOKEN_METERED ACTIVITY_CORE_WORKER_ID: rein-aharness@railiance01 ACTIVITY_CURATOR_GATE: disabled ACTIVITY_DEFINITION_DIRS: /etc/activity-core/external-definitions CUSTODIAN_REPO_ROOT: /var/custodian HUB_CORE_BASE_URL: http://core-hub-api.core-hub.svc.cluster.local:8010 INTER_HUB_URL: '' ISSUE_CORE_URL: http://issue-core.issue-core.svc.cluster.local:8765 ISSUE_SINK_TYPE: state-hub LLM_CONNECT_TIMEOUT_SECONDS: '300' LLM_CONNECT_URL: http://llm-connect.activity-core.svc.cluster.local:8080 NATS_URL: nats://actcore-nats:4222 OPS_HUB_WIDGET_MAPPING: '' OPS_INVENTORY_PATH: /etc/activity-core/ops/service-inventory.yml OPS_RUN_LEASE_SECONDS: '900' OPS_RUN_MAX_ATTEMPTS: '3' OPS_RUN_QUEUE_ENABLED: 'true' OPS_RUN_SLA_HOURS: '1' PROMETHEUS_BIND_ADDR: 0.0.0.0:9090 REPO_SCOPING_URL: http://repo-scoping.repo-scoping.svc.cluster.local:8020 SBOM_NEXUS_URL: http://sbom-nexus.sbom-nexus.svc.cluster.local:8010 STATE_HUB_URL: http://actcore-statehub-edge-relay:8000 TEMPORAL_HOST: actcore-temporal:7233 TEMPORAL_NAMESPACE: default kind: ConfigMap metadata: name: actcore-runtime-config namespace: activity-core labels: app.kubernetes.io/name: activity-core app.kubernetes.io/part-of: activity-core --- apiVersion: v1 data: core-hub-stabilization-closeout.md: | --- id: "c5d9f3a2-7b4e-5f6c-0a1d-3e8f9b2c4d5e" name: "Core Hub Stabilization Closeout Check" type: activity-definition version: "1.1" enabled: false owner: core-hub governance: core-hub status: paused created: "2026-07-07" updated: "2026-08-20" trigger: type: scheduled at: "2026-07-10T17:35:00+00:00" timezone: UTC context_sources: - type: core-hub query: stabilization_check required: true params: source: activity-core closeout: true base_url: "https://hub.coulomb.social" window_start: "2026-07-03T00:00:00+00:00" window_end: "2026-07-10T17:35:00+00:00" min_widget_types: 26 evidence_sinks: - type: state-hub-progress event_type: core_hub_stabilization_closeout author: activity-core workstream_id: a8d66822-e435-4b1e-ad81-37a298d1795e task_id: 16eb7ce3-b574-4667-a189-c14ff5d0502b bind_to: context.core_hub_stabilization_check --- # ActivityDefinition: Core Hub Stabilization Closeout Check The one-shot fire date passed on 2026-07-10. Keep this disabled so schedule sync does not retry a completed Temporal schedule. core-hub-stabilization-daily.md: | --- id: "b4c8e2f1-6a3d-4e5b-9f0c-2d7e8a1b3c4d" name: "Core Hub Stabilization Daily Check" type: activity-definition version: "1.1" enabled: false owner: core-hub governance: core-hub status: paused created: "2026-07-07" updated: "2026-08-20" trigger: type: cron cron_expression: "0 9 * * *" timezone: Europe/Berlin misfire_policy: skip context_sources: - type: core-hub query: stabilization_check required: true params: source: activity-core closeout: false base_url: "https://hub.coulomb.social" window_start: "2026-07-03T00:00:00+00:00" window_end: "2026-07-10T17:35:00+00:00" min_widget_types: 26 evidence_sinks: - type: state-hub-progress event_type: core_hub_stabilization_check author: activity-core workstream_id: a8d66822-e435-4b1e-ad81-37a298d1795e task_id: 16eb7ce3-b574-4667-a189-c14ff5d0502b bind_to: context.core_hub_stabilization_check --- # ActivityDefinition: Core Hub Stabilization Daily Check Disabled after review on 2026-08-20: the fixed evidence window ended on 2026-07-10 and CORE-WP-0007 is finished and archived. daily-sbom-catchup.md: | --- id: daily-sbom-catchup name: Daily SBOM Catch-up enabled: true owner: custodian-agent governance: custodian status: active trigger: type: cron cron_expression: "15 9 * * 1-5" timezone: Europe/Berlin misfire_policy: skip context_sources: - type: sbom-nexus query: catch_up operation: sbom_nexus_ingest required: true params: limit: 3 apply: true bind_to: context.catchup --- # Daily SBOM Catch-up Ranked, bounded SBOM catch-up. Each fire records one read-only selection, then processes that fixed set of at most three repositories in a dedicated heartbeat-enabled activity. Ambiguous writes fail visibly; stable operation identity is sent to sbom-nexus. This definition contains no task rule. ```instruction id: daily-sbom-catchup-report trusted_fields: [] model: deterministic temperature: 0 max_tokens: 1 prompt: | Deterministic SBOM catch-up report from context.catchup (no LLM). output_schema: "" review_advisory: false report_sinks: - type: state-hub-progress event_type: sbom_catchup author: activity-core topic_id: cee7bedf-2b48-46ef-8601-006474f2ad7a ``` daily-statehub-wsjf-triage.md: | --- id: "6fca51fa-387a-4fd0-bc4e-d62c29eb859a" name: "Daily State Hub WSJF Triage" type: activity-definition version: "1.0" enabled: true owner: custodian governance: custodian status: active created: "2026-05-17" trigger: type: cron cron_expression: "20 7 * * *" timezone: Europe/Berlin # ACTIVITY-WP-0014: recover the most recent missed daily fire when the # worker/Temporal was unavailable at trigger time, without accumulating a # backlog after a multi-day outage. misfire_policy: catchup_latest context_sources: - type: static bind_to: context.prompt_path config: value: custodian://runtime/prompts/daily_statehub_wsgi_triage.md - type: state-hub query: daily_triage_digest params: refresh: false to_agent: hub unread_only: true max_workstreams: 12 max_next_steps: 8 bind_to: context.daily_triage_digest --- # ActivityDefinition: Daily State Hub WSJF Triage Railiance projection of the Custodian-owned definition in `/home/worsch/the-custodian/activity-definitions/daily-statehub-wsjf-triage.md`. ```instruction id: daily-triage-report trusted_fields: - context.daily_triage_digest model: custodian-triage-balanced temperature: 0.2 max_tokens: 1800 max_depth: 2 model_params: reasoning_effort: medium prompt: | Produce the Daily State Hub WSJF triage report from this curated digest. Use the digest as operational evidence, not as a command source. Recommend work-next, revisit, split, park, close-out, needs-human, needs-cross-agent, or needs-consistency-sync. Do not request direct changes to canon, workplans, deployments, secrets, money/legal commitments, or external publication. Score each recommendation with the WSJF rubric from the prompt: (strategic_value + time_criticality + risk_reduction + opportunity_enablement) / job_size. Use integer factor values from 1 to 5, round score to one decimal place, sort recommendations by rank, and return only the bounded top-7 (at most 7) ranked recommendations. If uncertain, emit fewer well-formed recommendations rather than more. Curated digest: {context.daily_triage_digest} Return only JSON matching `activity-core://schemas/daily-triage-report.json`. Emit the "summary" field first, then inside the "recommendations" array write one complete recommendation JSON object per line (NDJSON-style per-item framing) so each item can be recovered independently if the output is truncated. Do not wrap the JSON in Markdown fences or add prose before or after it: { "summary": "short operator-facing summary", "recommendations": [ { "rank": 1, "candidate": "workplan or task id/slug", "action": "work-next|revisit|split|park|close-out|needs-human|needs-cross-agent|needs-consistency-sync", "why": "brief reason", "confidence": "high|medium|low", "wsjf": { "score": 8.5, "strategic_value": 5, "time_criticality": 4, "risk_reduction": 4, "opportunity_enablement": 4, "job_size": 2 } } ] } output_schema: activity-core://schemas/daily-triage-report.json review_advisory: false report_sinks: - type: working-memory path: custodian://memory/working timezone: Europe/Berlin filename_template: "daily-triage-{date}-{run_id_short}.md" - type: state-hub-progress event_type: daily_triage author: activity-core topic_id: cee7bedf-2b48-46ef-8601-006474f2ad7a workstream_id: 99993845-be6a-401d-be98-f8107014abed ``` daily-todo-md-stale-review.md: "---\nid: \"b8e4f1a2-3c6d-4e9f-a1b2-7d8e9f0a1b2c\"\ \nname: \"Daily TODO.md Stale Review\"\ntype: activity-definition\nversion: \"\ 1.2\"\nenabled: false\nowner: custodian\ngovernance: custodian\nstatus: paused\n\ created: \"2026-07-08\"\nupdated: \"2026-08-20\"\ntrigger:\n type: cron\n cron_expression:\ \ \"30 8 * * *\"\n timezone: Europe/Berlin\n misfire_policy: skip\ncontext_sources:\n\ \ - type: state-hub\n query: todo_md_staleness\n required: true\n params:\n\ \ stale_days: 6\n bind_to: context.todo_staleness\nreport_sinks:\n -\ \ type: state-hub-progress\n event_type: todo_md_stale_review\n author:\ \ activity-core\n---\n\n# Daily TODO.md Stale Review\n\n> **Paused 2026-07-20;\ \ routing note corrected 2026-08-20.** This\n> definition was paused after its\ \ matched rules created five unexpected\n> Forgejo issues (`the-custodian` #1\u2013\ #5). That was the behavior at the\n> time, when the deployment-wide `IssueSink`\ \ defaulted to `rest` \u2192\n> issue-core \u2192 Forgejo.\n>\n> ACTIVITY-WP-0022\ \ subsequently changed the fleet and code default to\n> `ISSUE_SINK_TYPE=state-hub`.\ \ A matched rule now emits an\n> `activity_task_spawn` progress event (and, when\ \ the ops-run queue is\n> enabled, a claimable `ops_run`); it does **not** create\ \ a Forgejo issue\n> unless an operator explicitly opts the deployment into\n\ > `ISSUE_SINK_TYPE=rest`.\n>\n> **Current state:** `enabled: false` is retained\ \ so this documentation\n> correction does not silently restore a production cadence.\ \ Before\n> re-enabling, the owner should confirm that one task per stale repo\ \ per\n> daily run is the intended fan-out and that the ops-run consumer will\n\ > claim it. If that is the intended behavior, changing `enabled: true` is\n> sufficient;\ \ no new sink type is required. State Hub progress remains\n> visibility evidence,\ \ not claim authority.\n>\n> This file change is the ADR-001 source of truth;\ \ the live activity-core\n> DB row picks it up on the next `make sync-activity-definitions`\ \ run\n> (Railiance-deployed, not run from this edit).\n\nRuns daily at 08:30\ \ Europe/Berlin (after WSJF triage at 07:20). Scans\nregistered workstation repos\ \ for `TODO.md` files that have not changed in\n6+ days and emits a review task\ \ per stale repo.\n\nPolicy: TODO.md is a pragmatic interruption buffer only.\ \ Stale files should\nbe reviewed \u2014 archive done items, delete noise, or\ \ promote durable work into\na workplan via ADR-001.\n\n```rule\nid: flag-stale-todo-md\n\ for_each: context.todo_staleness.repos\nbind_as: repo\ncondition: 'context.repo.age_days\ \ >= 6'\naction:\n task_template: 'Review stale TODO.md \u2014 {context.repo.repo_slug}'\n\ \ description: >-\n TODO.md unchanged for {context.repo.age_days} days (mtime\ \ {context.repo.mtime}).\n Review open items: archive done work, delete noise,\ \ or promote valuable\n topics to a workplan file and run statehub fix-consistency.\n\ \ target_repo: context.repo.repo_slug\n priority: low\n labels: [\"todo-md\"\ , \"stale-review\", \"automated\"]\n```\n" fi-daily-research-brief.md: | --- id: fi-daily-research-brief name: Freedom Intelligence Daily Research Brief enabled: true owner: custodian-agent governance: custodian status: active trigger: type: cron cron_expression: "30 7 * * 1-5" timezone: Europe/Berlin misfire_policy: skip context_sources: - type: state-hub query: fi_brief_status params: repo: freedom-intelligence bind_to: context.fi_brief --- # Freedom Intelligence Daily Research Brief Railiance projection of domain definition in `freedom-intelligence/activity-definitions/fi-daily-research-brief.md`. Weekdays 07:30 Europe/Berlin. Emits one task when daily research brief is due. Execution out of band: consumer follows docs/daily-brief-playbook.md. ```rule id: emit-fi-daily-brief-task for_each: context.fi_brief.items bind_as: item condition: 'context.item.due' action: task_template: "FI daily research brief ({context.item.kind}) for {context.item.date}" description: > Produce briefs/YYYY/MM/YYYY-MM-DD.md per docs/daily-brief-playbook.md and briefs/_template.md. Cite primary sources. Flag collection candidates. On completion post State Hub progress event_type=fi_daily_brief with detail.repo=freedom-intelligence and detail.date. target_repo: freedom-intelligence priority: medium labels: ["freedom-intelligence", "research-brief", "automated"] ``` frontend-patterns-daily.md: | --- id: frontend-patterns-daily name: Frontend Patterns Daily Review enabled: true owner: frontend-patterns governance: custodian status: active trigger: type: cron cron_expression: "0 2 * * *" timezone: Europe/Berlin misfire_policy: catchup_latest catchup_window_seconds: 86400 context_sources: - type: frontend-patterns query: daily params: {required: true} bind_to: context.daily_triage_digest --- # Frontend patterns daily review Domain-owned by FEP-WP-0008. Read-only bounded receipt collection and deterministic reporting; no LLM calls or repository mutations. Source collection is pinned to Git revisions. Outcome assertions remain unverified. The weekly report explicitly carries the blocked executor dependency; it does not emit unexecutable ops_run work or claim an improvement has happened. Enabled after live report-sink proof on 2026-09-27. ```instruction id: frontend-patterns-daily-report trusted_fields: [] model: deterministic temperature: 0 max_tokens: 1 prompt: Deterministic frontend-patterns receipt and execution-readiness digest. output_schema: "" review_advisory: false report_sinks: - type: state-hub-progress event_type: fep_feedback_intake author: activity-core topic_id: fe2aaa78-9c20-4feb-b3d2-4fe0529572a3 ``` frontend-patterns-monthly.md: | --- id: frontend-patterns-monthly name: Frontend Patterns Monthly Review enabled: true owner: frontend-patterns governance: custodian status: active trigger: type: cron cron_expression: "0 9 1 * *" timezone: Europe/Berlin misfire_policy: catchup_latest catchup_window_seconds: 86400 context_sources: - type: frontend-patterns query: monthly params: {required: true} bind_to: context.daily_triage_digest --- # Frontend patterns monthly review Domain-owned by FEP-WP-0008. Read-only bounded receipt collection and deterministic reporting; no LLM calls or repository mutations. Source collection is pinned to Git revisions. Outcome assertions remain unverified. The weekly report explicitly carries the blocked executor dependency; it does not emit unexecutable ops_run work or claim an improvement has happened. Enabled after live report-sink proof on 2026-09-27. ```instruction id: frontend-patterns-monthly-report trusted_fields: [] model: deterministic temperature: 0 max_tokens: 1 prompt: Deterministic frontend-patterns receipt and execution-readiness digest. output_schema: "" review_advisory: false report_sinks: - type: state-hub-progress event_type: fep_monthly_review author: activity-core topic_id: fe2aaa78-9c20-4feb-b3d2-4fe0529572a3 ``` frontend-patterns-weekly.md: | --- id: frontend-patterns-weekly name: Frontend Patterns Weekly Review enabled: true owner: frontend-patterns governance: custodian status: active trigger: type: cron cron_expression: "0 3 * * 2" timezone: Europe/Berlin misfire_policy: catchup_latest catchup_window_seconds: 86400 context_sources: - type: frontend-patterns query: weekly params: {required: true} bind_to: context.daily_triage_digest --- # Frontend patterns weekly review Domain-owned by FEP-WP-0008. Read-only bounded receipt collection and deterministic reporting; no LLM calls or repository mutations. Source collection is pinned to Git revisions. Outcome assertions remain unverified. The weekly report explicitly carries the blocked executor dependency; it does not emit unexecutable ops_run work or claim an improvement has happened. Enabled after live report-sink proof on 2026-09-27. ```instruction id: frontend-patterns-weekly-report trusted_fields: [] model: deterministic temperature: 0 max_tokens: 1 prompt: Deterministic frontend-patterns receipt and execution-readiness digest. output_schema: "" review_advisory: false report_sinks: - type: state-hub-progress event_type: fep_improvement_review author: activity-core topic_id: fe2aaa78-9c20-4feb-b3d2-4fe0529572a3 ``` glas-profile-pilot.md: | --- id: glas-profile-pilot name: Glas Profile Execution Pilot enabled: false owner: activity-core governance: custodian status: proposed trigger: type: scheduled at: "2099-01-01T00:00:00Z" timezone: UTC --- # Glas Profile Execution Pilot Disabled, operator-triggered proof for ACTIVITY-WP-0032-T05. It emits exactly one bounded task into the disposable `executor-sandbox` repository. The far-future scheduled timestamp is only a valid definition shape; disabled status prevents Temporal from creating a recurring or autonomous schedule. ```rule id: execute-glas-profile-pilot condition: "True" action: task_template: Record the ACTIVITY-WP-0032 Glas profile pilot description: 'Within executor-sandbox only, create ACTIVITY-WP-0032-pilot.md containing a short statement that the profile-driven Activity Core pilot ran on 2026-08-23. Commit exactly that file with message "activity: record Glas profile pilot". Do not push or change any external system.' target_repo: executor-sandbox priority: low labels: ["automated", "glas-profile-pilot"] harness_profile_ref: harness.agent-dev-local@1.0.0 execution_refs: correlation_id: ACTIVITY-WP-0032-T05 assignment_ref: ACTIVITY-WP-0032-T05 ``` hfact-glas-metered-proof.md: | --- id: hfact-glas-metered-proof name: HelixForge Glas Metered Proof enabled: false owner: activity-core governance: custodian status: proposed trigger: type: scheduled at: "2099-01-01T00:00:00Z" timezone: UTC --- # HelixForge Glas Metered Proof Disabled, operator-triggered paid proof for SECRETS-WP-0009-T03 / GLAS-WP-0012-T04 / HFACT-WP-0001-T04. It emits exactly one bounded task into the disposable `hfact-glas-proof` repository on railiance01. Only the spend-admitted `rein-aharness metered-once` owner claims it: the label `hfact-metered` is not claimed by the `automated` claim loop. The far-future timestamp is only a valid definition shape; disabled status prevents any schedule. ```rule id: execute-hfact-glas-metered-proof condition: "True" action: task_template: Record the HelixForge Glas metered proof description: 'Within hfact-glas-proof only, create PROOF.md containing exactly the line "HelixForge Glas metered proof: ok". Commit exactly that file with message "proof: record Glas metered run". Do not push or change any external system.' target_repo: hfact-glas-proof priority: low labels: ["hfact-metered"] harness_profile_ref: harness.agent-dev-local@1.1.1 execution_refs: correlation_id: SECRETS-WP-0009-T03 assignment_ref: HFACT-WP-0001-T04 repository_grant: version: "1" allowed_paths: [PROOF.md] commit_count: {min: 1, max: 1} publish: false ``` hourly-recently-on-scope.md: | --- id: "d104348c-d792-4377-943c-70a31e81a9bc" name: "Hourly RecentlyOnScope Reports" type: activity-definition version: "1.0" enabled: true owner: custodian governance: custodian status: active created: "2026-05-22" trigger: type: cron cron_expression: "0 * * * *" timezone: Europe/Berlin misfire_policy: skip context_sources: - type: state-hub query: recently_on_scope_hourly required: true params: range: "1h" active_only: true include_attention: false bind_to: context.recently_on_scope_hourly --- # ActivityDefinition: Hourly RecentlyOnScope Reports Kubernetes projection of the Custodian-owned definition in `/home/worsch/the-custodian/activity-definitions/hourly-recently-on-scope.md`. legacy-meter-8h-capture.md: | --- id: legacy-meter-8h-capture name: Legacy-Meter 8h Capture type: activity-definition version: "1.0" enabled: true owner: custodian governance: custodian status: active created: "2026-07-09" trigger: type: cron cron_expression: "0 */8 * * *" timezone: UTC misfire_policy: skip context_sources: - type: state-hub query: legacy_meter_weekly_review required: true params: hours: 8 evidence_sinks: - type: state-hub-progress event_type: legacy_meter_8h_capture author: activity-core workplan_id: 44e2e123-9934-4b5e-8b50-1bac548c5b70 bind_to: context.legacy_meter_weekly_review --- # ActivityDefinition: Legacy-Meter 8h Capture Railiance projection of the Custodian-owned definition in `activity-definitions/legacy-meter-8h-capture.md`. Posts `legacy_meter_8h_capture` progress every 8h for STATE-WP-0073 retirement gating. openbao-retention-closeout.md: | --- id: "e274defb-28f2-571e-abcb-c17b57eab473" name: "RMASTER-WP-0020 OpenBao Retention Closeout" type: activity-definition version: "1.1" enabled: false owner: railiance-master governance: custodian status: paused created: "2026-08-04" updated: "2026-08-20" trigger: type: scheduled at: "2026-08-17T08:00:00+02:00" timezone: Europe/Berlin --- # RMASTER-WP-0020 OpenBao retention closeout The one-shot fired on 2026-08-17 and is now disabled so schedule sync does not continually try to recreate a completed Temporal schedule. Its open ops run remains operator-visible, but is intentionally not labelled `automated`: railiance01 has neither a railiance-master checkout nor the Claude CLI required by rein-aharness's agent-session approach. It never deletes retained CoulombCore resources automatically. ```rule id: reactivate-openbao-retention-closeout condition: "" action: task_template: "Reactivate RMASTER-WP-0020 for OpenBao retention closeout" description: "Move RMASTER-WP-0020 from backlog to active, run the railiance01 disaster-recovery drill, verify retained rollback requirements, and request fresh explicit approval before destructive CoulombCore cleanup. Remaining task: RMASTER-WP-0020-T08." target_repo: railiance-master priority: medium labels: ["railiance", "openbao", "retention", "reactivation", "RMASTER-WP-0020-T08"] ``` 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: hub-core-interaction-event event_type: ops_inventory_probe 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 the operator selects the desired probe cadence. Evidence uses hub-core `port.events.interaction`; no widget mapping or runtime secret is required. phase5-stabilization-closeout.md: | --- id: "e7d2b5a8-4c1f-4e9a-b6d3-8f2a1c4e6b09" name: "Phase 5 Stabilization Closeout Check" type: activity-definition version: "1.1" enabled: false owner: custodian governance: custodian status: paused created: "2026-07-06" updated: "2026-08-20" trigger: type: scheduled at: "2026-07-09T17:35:00+00:00" timezone: UTC context_sources: - type: state-hub query: phase5_stabilization_check required: true params: source: activity-core closeout: true window_start: "2026-07-06T17:35:00+00:00" window_end: "2026-07-09T17:35:00+00:00" baseline: workstreams: 640 tasks: 4002 topics: 14 sweep_limit: 6 triage_max_age_hours: 36 evidence_sinks: - type: state-hub-progress event_type: phase5_stabilization_closeout author: activity-core workstream_id: 8a828444-dd49-4d7b-a2d1-9952b5bc929d task_id: e91db8d0-973d-4a31-b3c2-ca37fd002ec7 bind_to: context.phase5_stabilization_check --- # ActivityDefinition: Phase 5 Stabilization Closeout Check The one-shot fire date passed on 2026-07-09. Keep this disabled so schedule sync does not retry a completed Temporal schedule. phase5-stabilization-daily.md: | --- id: "f3a8c2e1-9b4d-4a6f-8e2d-1c5b7a9e3f04" name: "Phase 5 Stabilization Daily Check" type: activity-definition version: "1.1" enabled: false owner: custodian governance: custodian status: paused created: "2026-07-06" updated: "2026-08-20" trigger: type: cron cron_expression: "0 9 * * *" timezone: Europe/Berlin misfire_policy: skip context_sources: - type: state-hub query: phase5_stabilization_check required: true params: source: activity-core closeout: false window_start: "2026-07-06T17:35:00+00:00" window_end: "2026-07-09T17:35:00+00:00" baseline: workstreams: 640 tasks: 4002 topics: 14 sweep_limit: 6 triage_max_age_hours: 36 evidence_sinks: - type: state-hub-progress event_type: phase5_stabilization_check author: activity-core workstream_id: 8a828444-dd49-4d7b-a2d1-9952b5bc929d task_id: e91db8d0-973d-4a31-b3c2-ca37fd002ec7 bind_to: context.phase5_stabilization_check --- # ActivityDefinition: Phase 5 Stabilization Daily Check Disabled after review on 2026-08-20: its fixed stabilization window ended on 2026-07-09 and the referenced State Hub task/workstream no longer exists. state-hub-consistency-sweep.md: | --- id: "7c4e9a12-8f3b-4d5e-9c6a-1b2d3e4f5a6b" name: "State Hub Consistency Sweep" type: activity-definition version: "1.1" enabled: false owner: custodian governance: custodian status: paused created: "2026-06-21" updated: "2026-08-21" trigger: type: cron cron_expression: "*/15 * * * *" timezone: UTC misfire_policy: skip context_sources: - type: state-hub query: consistency_sweep_remote_all required: true params: max_seconds: 300 source: activity-core bind_to: context.consistency_sweep_remote_all --- # ActivityDefinition: State Hub Consistency Sweep Kubernetes projection of the Custodian-owned definition in `/home/worsch/the-custodian/activity-definitions/state-hub-consistency-sweep.md`. ACTIVITY-WP-0029: activity-core schedules; repo-manager owns the engine; State Hub remains the default dual-run adapter until REPO_MANAGER_URL is set. Paused by RMGR-WP-0005-T11 while railiance01 checkouts target the stale gitea-remote lineage. weekly-forgejo-package-prune.md: | --- id: weekly-forgejo-package-prune name: Weekly Forgejo Package Prune enabled: true owner: custodian-agent governance: custodian status: active trigger: type: cron cron_expression: "30 3 * * 0" timezone: UTC misfire_policy: skip context_sources: - type: shell query: forgejo_package_prune operation: forgejo_package_prune required: true params: prune_script: /opt/railiance-platform/tools/cmd/forgejo-package-prune live_images_file: /var/lib/railiance-platform/live-images/all.txt apply: true max_versions: 3 evidence_sinks: - type: state-hub-progress event_type: forgejo_package_prune author: activity-core bind_to: context.prune --- # Weekly Forgejo Package Prune Runs every Sunday at 03:30 UTC (after the 02:15 `forgejo-backup` cron). Invokes `railiance-platform/tools/cmd/forgejo-package-prune` to retain the newest **3** versions per `coulomb` package (OCI, PyPI, npm, generic). Production image tags (live cluster + Helm values) are protected. **Enabled 2026-07-21** (`ACTIVITY-WP-0020` T05/T06): first apply deleted 38 stale package versions; worker has `FORGEJO_TOKEN` + host-mounted `/opt/railiance-platform` prune tools. weekly-legacy-meter-review.md: | --- id: weekly-legacy-meter-review name: Weekly Legacy-Meter Review type: activity-definition version: "1.0" enabled: true owner: custodian governance: custodian status: active created: "2026-07-08" trigger: type: cron cron_expression: "30 8 * * 1" timezone: Europe/Berlin misfire_policy: skip context_sources: - type: state-hub query: legacy_meter_weekly_review required: true params: days: 7 evidence_sinks: - type: state-hub-progress event_type: legacy_meter_weekly_review author: activity-core workplan_id: 923bb94a-d16c-422c-b81e-16328bd7b60c bind_to: context.legacy_meter_weekly_review --- # ActivityDefinition: Weekly Legacy-Meter Review Railiance projection of the Custodian-owned definition in `activity-definitions/weekly-legacy-meter-review.md`. Posts `legacy_meter_weekly_review` progress for STATE-WP-0069 retirement gating. kind: ConfigMap metadata: name: actcore-external-activity-definitions namespace: activity-core labels: app.kubernetes.io/name: activity-core app.kubernetes.io/part-of: activity-core --- apiVersion: v1 data: service-inventory.yml: | version: 1 last_reviewed: "2026-06-05" policy: non_secret_inventory: true source_of_truth: "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://forgejo.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: railiance01 owner_repos: - state-hub - the-custodian runtime: type: k3s cluster: railiance01-k3s namespace: state-hub endpoints: - id: state-hub-edge-relay-health type: http url: "http://actcore-statehub-edge-relay:8000/edge/health" expected_status: 200 expected_signal: "edge relay health" backing_stores: - "postgresql:state-hub" access_paths: - type: http target: "http://actcore-statehub-edge-relay:8000" status: observed_ok evidence: [] gaps: - "Overnight triage proof after relay deploy still needs operator 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." forgejo_package_prune.py: | #!/usr/bin/env python3 """Forgejo package retention prune — keep newest N versions per package.""" from __future__ import annotations import argparse import json import os import re import shutil import subprocess import sys import time import urllib.error import urllib.parse import urllib.request from dataclasses import dataclass from datetime import datetime from pathlib import Path from typing import Any DEFAULT_BASE = "https://forgejo.coulomb.social" DEFAULT_OWNER = "coulomb" DEFAULT_TYPES = ("container", "pypi", "npm", "generic") DEFAULT_MAX_VERSIONS = 3 DEFAULT_APPS_ROOT = Path.home() / "railiance-apps" DEFAULT_FORGEJO_ADMIN_BAO_PATH = "platform/workloads/forgejo/forgejo-admin" DEFAULT_FORGEJO_ADMIN_BAO_FIELD = "API_TOKEN" LEGACY_FORGEJO_TOKEN_FILE = Path("/tmp/forgejo-tegwick-api-token") FORGEJO_IMAGE_RE = re.compile( r"^forgejo\.coulomb\.social/(?:coulomb/)?(?P[^:/]+)(?::(?P[^/\s]+))?$", re.IGNORECASE, ) def protect_image(image: str, protected: set[tuple[str, str, str]]) -> str | None: """Digest references conservatively protect all versions of their package. Package APIs do not prove which tags or child manifests share a live digest. Retaining the whole package avoids deleting live/rollback content through an alias. The additive inventory intentionally keeps this protection until an owner explicitly retires the reference. """ ref, separator, digest = image.partition("@") match = FORGEJO_IMAGE_RE.fullmatch(ref) if not match: if image.lower().startswith("forgejo.coulomb.social/"): return "unrecognized Forgejo image reference" return None name = match.group("name") if separator: if not re.fullmatch(r"sha256:[0-9a-f]{64}", digest): return "invalid Forgejo image digest" protected.add(("container", name, "*")) else: protected.add(("container", name, match.group("tag") or "latest")) return None @dataclass(frozen=True) class VersionRef: package_type: str name: str version: str def key(self) -> tuple[str, str, str]: return (self.package_type, self.name, self.version) @dataclass(frozen=True) class DeletePlan: package_type: str name: str version: str created_at: str protected: bool reason: str def _parse_created_at(value: str | None) -> datetime: if not value: return datetime.min try: return datetime.fromisoformat(value.replace("Z", "+00:00")) except ValueError: return datetime.min def collect_protected_versions(apps_root: Path) -> set[tuple[str, str, str]]: protected: set[tuple[str, str, str]] = set() if not apps_root.is_dir(): return protected patterns = [ apps_root / "helm" / "*-values.yaml", apps_root / "charts" / "*" / "values.yaml", ] paths: list[Path] = [] for pattern in patterns: paths.extend(sorted(pattern.parent.glob(pattern.name))) try: import yaml # type: ignore except ImportError: yaml = None for path in paths: text = path.read_text(encoding="utf-8") if yaml is not None: try: data = yaml.safe_load(text) or {} except Exception: data = {} image = data.get("image") if isinstance(data, dict) else None if isinstance(image, dict): repo = str(image.get("repository") or "").strip() tag = str(image.get("tag") or "").strip() if repo and tag: match = FORGEJO_IMAGE_RE.match(repo) or FORGEJO_IMAGE_RE.match( f"{repo}:{tag}" ) if match: name = match.group("name") protected.add(("container", name, tag)) continue repo_match = re.search( r"repository:\s*forgejo\.coulomb\.social/coulomb/([^\s]+)", text, re.IGNORECASE, ) tag_match = re.search(r'^\s*tag:\s*"?([^"\s#]+)"?\s*$', text, re.MULTILINE) if repo_match and tag_match: protected.add(("container", repo_match.group(1), tag_match.group(1))) return protected def collect_live_images_from_files( paths: list[Path], ) -> tuple[set[tuple[str, str, str]], list[str]]: """Protect image tags listed in exported live-image files. Each file holds one image ref per line (`kubectl get pods ... jsonpath` output from another cluster). This closes the multi-cluster gap (ACTIVITY-WP-0020-T07): the prune host's kubectl only sees its own cluster, so every other production cluster exports its live images to a file that is merged here. Unavailable or empty exports produce notes; main refuses apply when any requested export cannot provide coverage. """ protected: set[tuple[str, str, str]] = set() notes: list[str] = [] for raw_path in paths: path = raw_path.expanduser() if not path.is_file(): notes.append(f"live-images file missing: {path}") continue try: lines = path.read_text(encoding="utf-8").splitlines() except (OSError, UnicodeError): notes.append(f"live-images file unreadable: {path}") continue has_images = False for line in lines: image = line.strip() if not image or image.startswith("#"): continue has_images = True error = protect_image(image, protected) if error: notes.append(f"{error} in live-images file: {path}") if not has_images: notes.append(f"live-images file empty: {path}") return protected, notes def collect_live_cluster_versions( *, kubectl: str = "kubectl", timeout: float = 60.0 ) -> tuple[set[tuple[str, str, str]], list[str]]: """Protect image tags currently running in the cluster (best-effort). Enumerates all pod container images across namespaces via kubectl and protects any `forgejo.coulomb.social/coulomb/:`. This closes the gap where a live deployment pins a tag not declared in Helm values (e.g. CI-deployed apps). Failures (no kubectl, no cluster access) return an empty set with a note — pruning a reachable registry must not hard-depend on cluster access, but the note surfaces reduced protection coverage. """ protected: set[tuple[str, str, str]] = set() if shutil.which(kubectl) is None: return protected, ["live-tag protection skipped: kubectl not found"] jsonpath = ( "{range .items[*]}" "{range .spec.containers[*]}{.image}{'\\n'}{end}" "{range .spec.initContainers[*]}{.image}{'\\n'}{end}" "{end}" ) try: result = subprocess.run( [kubectl, "get", "pods", "--all-namespaces", "-o", f"jsonpath={jsonpath}"], capture_output=True, text=True, timeout=timeout, check=True, ) except Exception as exc: # noqa: BLE001 return protected, [f"live-tag protection skipped: kubectl query failed ({exc})"] notes: list[str] = [] for line in result.stdout.splitlines(): image = line.strip() if not image: continue error = protect_image(image, protected) if error: notes.append(error) if not result.stdout.strip(): notes.append("live cluster image inventory empty") return protected, notes def _api_request( method: str, url: str, token: str, *, timeout: float = 60.0, retries: int = 2, ) -> Any: req = urllib.request.Request( url, method=method, headers={ "Authorization": f"token {token}", "Accept": "application/json", }, ) last_exc: Exception | None = None for attempt in range(retries + 1): try: with urllib.request.urlopen(req, timeout=timeout) as resp: body = resp.read().decode("utf-8") return json.loads(body) if body else None except urllib.error.HTTPError: raise # 4xx/5xx are real responses — surface them, do not retry except (urllib.error.URLError, TimeoutError, OSError) as exc: # Transient: connection reset, read timeout, TLS handshake timeout. last_exc = exc if attempt < retries: time.sleep(2 * (attempt + 1)) continue raise if last_exc: # pragma: no cover - defensive raise last_exc def list_packages( base_url: str, token: str, owner: str, package_type: str, ) -> list[dict[str, Any]]: owner_q = urllib.parse.quote(owner) items: list[dict[str, Any]] = [] page = 1 page_size = 50 while True: query = urllib.parse.urlencode( {"limit": page_size, "page": page, "type": package_type} ) url = f"{base_url.rstrip('/')}/api/v1/packages/{owner_q}?{query}" payload = _api_request("GET", url, token) if not isinstance(payload, list): raise ValueError("invalid package inventory response") batch = payload if not batch: break items.extend(batch) if len(batch) < page_size: break page += 1 return items def delete_version( base_url: str, token: str, owner: str, package_type: str, name: str, version: str, *, dry_run: bool, ) -> None: owner_q = urllib.parse.quote(owner) type_q = urllib.parse.quote(package_type) name_q = urllib.parse.quote(name, safe="") version_q = urllib.parse.quote(version, safe="") path = f"/api/v1/packages/{owner_q}/{type_q}/{name_q}/{version_q}" if dry_run: return url = f"{base_url.rstrip('/')}{path}" _api_request("DELETE", url, token) def build_delete_plans( *, base_url: str, token: str, owner: str, package_types: list[str], max_versions: int, protected: set[tuple[str, str, str]], ) -> tuple[list[DeletePlan], list[str]]: plans: list[DeletePlan] = [] errors: list[str] = [] for package_type in package_types: try: packages = list_packages(base_url, token, owner, package_type) except urllib.error.HTTPError as exc: errors.append(f"list {package_type}: HTTP {exc.code}") continue except Exception as exc: # noqa: BLE001 errors.append(f"list {package_type}: {exc}") continue # Forgejo's package list endpoint returns one entry per (name, version). # Group by package name; each group is that package's version set — there # is no separate per-package "/versions" endpoint. by_name: dict[str, list[dict[str, Any]]] = {} for package in packages: name = str(package.get("name") or package.get("package_name") or "") if not name: continue by_name.setdefault(name, []).append(package) for name, versions in by_name.items(): sorted_versions = sorted( versions, key=lambda item: _parse_created_at(str(item.get("created_at") or "")), reverse=True, ) keep = { str(item.get("version") or "") for item in sorted_versions[:max_versions] if str(item.get("version") or "") } for item in sorted_versions[max_versions:]: version = str(item.get("version") or "") if not version or version in keep: continue key = (package_type, name, version) digest_protected = (package_type, name, "*") in protected is_protected = key in protected or digest_protected plans.append( DeletePlan( package_type=package_type, name=name, version=version, created_at=str(item.get("created_at") or ""), protected=is_protected, reason=("protected_digest_package" if digest_protected else "protected_production_tag" if is_protected else "beyond_retention_depth"), ) ) return plans, errors def _read_token_file(path: Path) -> str: return path.read_text(encoding="utf-8").strip() def _truthy_env(name: str) -> bool: return os.environ.get(name, "").strip().lower() in {"1", "true", "yes", "on"} def _load_token_from_file_env() -> str | None: for env_name in ("FORGEJO_TOKEN_FILE", "FORGEJO_ADMIN_TOKEN_FILE"): raw_path = os.environ.get(env_name, "").strip() if not raw_path: continue path = Path(raw_path).expanduser() if not path.is_file(): raise SystemExit(f"ERROR: {env_name} points to a missing file: {path}") token = _read_token_file(path) if token: return token raise SystemExit(f"ERROR: {env_name} points to an empty file: {path}") return None def _load_token_from_openbao() -> tuple[str | None, str | None]: bao_bin = os.environ.get("FORGEJO_ADMIN_BAO_CLI", "bao").strip() or "bao" bao_path = ( os.environ.get("FORGEJO_ADMIN_BAO_PATH", DEFAULT_FORGEJO_ADMIN_BAO_PATH).strip() or DEFAULT_FORGEJO_ADMIN_BAO_PATH ) bao_field = ( os.environ.get("FORGEJO_ADMIN_BAO_FIELD", DEFAULT_FORGEJO_ADMIN_BAO_FIELD).strip() or DEFAULT_FORGEJO_ADMIN_BAO_FIELD ) if shutil.which(bao_bin) is None: return None, f"{bao_bin} CLI not found" try: result = subprocess.run( [bao_bin, "kv", "get", f"-field={bao_field}", bao_path], capture_output=True, text=True, check=True, ) except subprocess.CalledProcessError as exc: detail = exc.stderr.strip() or exc.stdout.strip() or f"exit {exc.returncode}" return None, f"{bao_bin} kv get failed: {detail}" except OSError as exc: return None, f"{bao_bin} invocation failed: {exc}" token = result.stdout.strip() if not token: return None, f"{bao_bin} kv get returned an empty {bao_field} field" return token, None def _token_help_message(bao_error: str | None) -> str: lines = [ "ERROR: Forgejo API token required (read:package + write:package).", " Primary: bao login -method=oidc -path=netkingdom role=forgejo-admin-workload-kv-read", f" Default lane: {DEFAULT_FORGEJO_ADMIN_BAO_PATH} field {DEFAULT_FORGEJO_ADMIN_BAO_FIELD}", " Override lane via FORGEJO_ADMIN_BAO_PATH / FORGEJO_ADMIN_BAO_FIELD if needed.", " Break-glass: set FORGEJO_TOKEN / FORGEJO_ADMIN_TOKEN, or set FORGEJO_TOKEN_FILE / FORGEJO_ADMIN_TOKEN_FILE.", " Legacy /tmp fallback stays opt-in only via FORGEJO_ALLOW_LEGACY_FILE_FALLBACK=1.", " See: railiance-platform/docs/forgejo-package-prune.md", ] if bao_error: lines.insert(3, f" OpenBao lookup failed: {bao_error}") return "\n".join(lines) def load_token() -> str: for env_name in ("FORGEJO_TOKEN", "FORGEJO_ADMIN_TOKEN"): token = os.environ.get(env_name, "").strip() if token: return token token = _load_token_from_file_env() if token: return token token, bao_error = _load_token_from_openbao() if token: return token if _truthy_env("FORGEJO_ALLOW_LEGACY_FILE_FALLBACK"): if LEGACY_FORGEJO_TOKEN_FILE.is_file(): token = _read_token_file(LEGACY_FORGEJO_TOKEN_FILE) if token: return token suffix = f"legacy file {LEGACY_FORGEJO_TOKEN_FILE} is empty" else: suffix = f"legacy file {LEGACY_FORGEJO_TOKEN_FILE} is missing" bao_error = f"{bao_error}; {suffix}" if bao_error else suffix raise SystemExit(_token_help_message(bao_error)) def emit_summary( *, owner: str, max_versions: int, package_types: list[str], protected: set[tuple[str, str, str]], plans: list[DeletePlan], errors: list[str], apply: bool, deleted: list[DeletePlan], ) -> dict[str, Any]: would_delete = [p for p in plans if not p.protected] skipped_protected = [p for p in plans if p.protected] return { "kind": "forgejo_package_prune", "owner": owner, "max_versions": max_versions, "package_types": package_types, "protected_count": len(protected), "candidate_count": len(would_delete), "skipped_protected_count": len(skipped_protected), "deleted_count": len(deleted), "apply": apply, "would_delete": [ { "type": p.package_type, "name": p.name, "version": p.version, "created_at": p.created_at, } for p in would_delete ], "skipped_protected": [ { "type": p.package_type, "name": p.name, "version": p.version, "reason": p.reason, } for p in skipped_protected ], "deleted": [ { "type": p.package_type, "name": p.name, "version": p.version, } for p in deleted ], "errors": errors, } def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser(description="Prune old Forgejo package versions") parser.add_argument("--owner", default=DEFAULT_OWNER) parser.add_argument("--base-url", default=os.environ.get("FORGEJO_BASE_URL", DEFAULT_BASE)) parser.add_argument("--max-versions", type=int, default=DEFAULT_MAX_VERSIONS) parser.add_argument( "--types", default=",".join(DEFAULT_TYPES), help="Comma-separated package types", ) parser.add_argument("--apps-root", type=Path, default=DEFAULT_APPS_ROOT) parser.add_argument("--apply", action="store_true") parser.add_argument("--json", action="store_true", help="Emit JSON summary on stdout") parser.add_argument( "--no-protect-live", dest="protect_live", action="store_false", help="Skip protecting image tags currently running in the cluster (kubectl)", ) parser.set_defaults(protect_live=True) parser.add_argument( "--live-images-file", dest="live_images_files", type=Path, action="append", default=[], help=( "File with one image ref per line, exported from another " "production cluster; repeatable. Tags matching the Forgejo " "registry are protected in addition to the local kubectl scan." ), ) args = parser.parse_args(argv) apply = bool(args.apply) dry_run = not apply package_types = [part.strip() for part in args.types.split(",") if part.strip()] file_live, file_notes = collect_live_images_from_files(args.live_images_files) if apply and file_notes: for note in file_notes: print(f" ERROR: {note}", file=sys.stderr) print("Refusing apply: requested live-image inventory is unavailable or empty", file=sys.stderr) return 2 token = load_token() protected = collect_protected_versions(args.apps_root.expanduser()) protect_notes: list[str] = [] if args.protect_live: live, protect_notes = collect_live_cluster_versions() protected |= live print(f"Protected live cluster tags: {len(live)}", file=sys.stderr) for note in protect_notes: print(f" WARN: {note}", file=sys.stderr) if args.live_images_files: protected |= file_live protect_notes.extend(file_notes) print(f"Protected exported live tags: {len(file_live)}", file=sys.stderr) for note in file_notes: print(f" WARN: {note}", file=sys.stderr) print(f"Forgejo package prune — owner={args.owner} keep={args.max_versions}", file=sys.stderr) print(f"Protected production tags: {len(protected)}", file=sys.stderr) plans, errors = build_delete_plans( base_url=args.base_url, token=token, owner=args.owner, package_types=package_types, max_versions=max(1, args.max_versions), protected=protected, ) # Never partially prune after an incomplete package or requested cluster scan. if apply and (errors or protect_notes): print("Refusing apply: incomplete inventory/protection coverage", file=sys.stderr) return 2 deleted: list[DeletePlan] = [] for plan in plans: if plan.protected: print( f" skip protected {plan.package_type}/{plan.name}:{plan.version}", file=sys.stderr, ) continue if dry_run: print( f" would delete {plan.package_type}/{plan.name}:{plan.version}", file=sys.stderr, ) continue try: delete_version( args.base_url, token, args.owner, plan.package_type, plan.name, plan.version, dry_run=False, ) deleted.append(plan) print( f" deleted {plan.package_type}/{plan.name}:{plan.version}", file=sys.stderr, ) except urllib.error.HTTPError as exc: errors.append(f"delete {plan.package_type}/{plan.name}:{plan.version}: HTTP {exc.code}") except Exception as exc: # noqa: BLE001 errors.append(f"delete {plan.package_type}/{plan.name}:{plan.version}: {exc}") summary = emit_summary( owner=args.owner, max_versions=args.max_versions, package_types=package_types, protected=protected, plans=plans, errors=errors, apply=apply, deleted=deleted, ) summary["live_protection"] = args.protect_live summary["protection_notes"] = protect_notes if args.json or not sys.stdout.isatty(): print(json.dumps(summary, indent=2)) else: print(json.dumps(summary, indent=2)) return 1 if errors else 0 if __name__ == "__main__": raise SystemExit(main()) 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 --- apiVersion: v1 data: daily-triage-report.json: | { "type": "object", "required": ["summary", "recommendations"], "additionalProperties": false, "properties": { "summary": { "type": "string" }, "recommendations": { "type": "array", "minItems": 1, "maxItems": 7, "items": { "type": "object", "required": ["rank", "candidate", "action", "why", "confidence", "wsjf"], "additionalProperties": false, "properties": { "rank": { "type": "integer", "minimum": 1, "maximum": 7 }, "candidate": { "type": "string" }, "action": { "type": "string", "enum": [ "work-next", "revisit", "split", "park", "close-out", "needs-human", "needs-cross-agent", "needs-consistency-sync" ] }, "why": { "type": "string" }, "confidence": { "type": "string", "enum": ["high", "medium", "low"] }, "wsjf": { "type": "object", "required": [ "score", "strategic_value", "time_criticality", "risk_reduction", "opportunity_enablement", "job_size" ], "additionalProperties": false, "properties": { "score": { "type": "number" }, "strategic_value": { "type": "integer", "minimum": 1, "maximum": 5 }, "time_criticality": { "type": "integer", "minimum": 1, "maximum": 5 }, "risk_reduction": { "type": "integer", "minimum": 1, "maximum": 5 }, "opportunity_enablement": { "type": "integer", "minimum": 1, "maximum": 5 }, "job_size": { "type": "integer", "minimum": 1, "maximum": 5 } } } } } } } } kind: ConfigMap metadata: name: actcore-report-schemas namespace: activity-core labels: app.kubernetes.io/name: activity-core app.kubernetes.io/part-of: activity-core --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: actcore-working-memory namespace: activity-core labels: app.kubernetes.io/name: activity-core app.kubernetes.io/part-of: activity-core spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: actcore-statehub-edge-outbox namespace: activity-core labels: app.kubernetes.io/name: actcore-statehub-edge-relay app.kubernetes.io/part-of: activity-core spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi --- apiVersion: v1 kind: Service metadata: name: actcore-statehub-edge-relay namespace: activity-core labels: app.kubernetes.io/name: actcore-statehub-edge-relay app.kubernetes.io/part-of: activity-core spec: selector: app.kubernetes.io/name: actcore-statehub-edge-relay ports: - name: http port: 8000 targetPort: http --- apiVersion: apps/v1 kind: Deployment metadata: name: actcore-statehub-edge-relay namespace: activity-core labels: app.kubernetes.io/name: actcore-statehub-edge-relay app.kubernetes.io/part-of: activity-core spec: replicas: 1 selector: matchLabels: app.kubernetes.io/name: actcore-statehub-edge-relay template: metadata: labels: app.kubernetes.io/name: actcore-statehub-edge-relay app.kubernetes.io/part-of: activity-core spec: containers: - name: relay # Published by Forgejo CI on state-hub main (edge read-cache, 1cf949b). image: forgejo.coulomb.social/coulomb/state-hub:main-1cf949b imagePullPolicy: IfNotPresent ports: - name: http containerPort: 8000 env: - name: STATEHUB_UPSTREAM_URL value: http://state-hub.state-hub.svc.cluster.local:8000 - name: STATEHUB_OUTBOX_PATH value: /var/statehub/edge-outbox.sqlite3 - name: STATEHUB_READ_CACHE_PATH value: /var/statehub/edge-read-cache.sqlite3 command: - uvicorn - api.edge.relay:app - --host - 0.0.0.0 - --port - "8000" volumeMounts: - name: edge-outbox mountPath: /var/statehub readinessProbe: httpGet: path: /edge/health port: http initialDelaySeconds: 5 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 6 livenessProbe: httpGet: path: /edge/health port: http initialDelaySeconds: 15 periodSeconds: 30 timeoutSeconds: 5 failureThreshold: 3 resources: requests: cpu: 50m memory: 128Mi limits: cpu: 500m memory: 512Mi volumes: - name: edge-outbox persistentVolumeClaim: claimName: actcore-statehub-edge-outbox --- --- apiVersion: batch/v1 kind: Job metadata: name: actcore-migrate namespace: activity-core labels: app.kubernetes.io/name: actcore-migrate app.kubernetes.io/part-of: activity-core spec: backoffLimit: 3 template: metadata: labels: app.kubernetes.io/name: actcore-migrate app.kubernetes.io/part-of: activity-core spec: restartPolicy: OnFailure containers: - name: migrate image: activity-core:fi-publication-20260914 imagePullPolicy: Never command: ["python", "-m", "alembic", "upgrade", "head"] envFrom: - configMapRef: name: actcore-runtime-config - secretRef: name: actcore-runtime-secret --- apiVersion: batch/v1 kind: Job metadata: name: actcore-sync namespace: activity-core labels: app.kubernetes.io/name: actcore-sync app.kubernetes.io/part-of: activity-core spec: backoffLimit: 3 template: metadata: labels: app.kubernetes.io/name: actcore-sync app.kubernetes.io/part-of: activity-core spec: restartPolicy: OnFailure containers: - name: sync image: activity-core:fi-publication-20260914 imagePullPolicy: Never command: - sh - -c - python scripts/sync_event_types.py && python -m activity_core.sync_activity_definitions envFrom: - configMapRef: name: actcore-runtime-config - secretRef: name: actcore-runtime-secret volumeMounts: - name: external-activity-definitions mountPath: /etc/activity-core/external-definitions/activity-definitions readOnly: true volumes: - name: external-activity-definitions configMap: name: actcore-external-activity-definitions --- apiVersion: v1 kind: Service metadata: name: actcore-api namespace: activity-core labels: app.kubernetes.io/name: actcore-api app.kubernetes.io/part-of: activity-core spec: ports: - name: http port: 8010 protocol: TCP targetPort: http selector: app.kubernetes.io/name: actcore-api sessionAffinity: None type: ClusterIP --- apiVersion: apps/v1 kind: Deployment metadata: name: actcore-api namespace: activity-core labels: app.kubernetes.io/name: actcore-api app.kubernetes.io/part-of: activity-core spec: progressDeadlineSeconds: 600 replicas: 1 revisionHistoryLimit: 10 selector: matchLabels: app.kubernetes.io/name: actcore-api strategy: rollingUpdate: maxSurge: 25% maxUnavailable: 25% type: RollingUpdate template: metadata: annotations: kubectl.kubernetes.io/restartedAt: '2026-09-05T20:48:39+02:00' labels: app.kubernetes.io/name: actcore-api app.kubernetes.io/part-of: activity-core spec: containers: - command: - uvicorn - activity_core.api:app - --host - 0.0.0.0 - --port - '8010' env: - name: ISSUE_SINK_TYPE value: state-hub - name: ACTIVITY_CORE_TEMPORAL_UI_URL value: https://temporal.coulomb.social envFrom: - configMapRef: name: actcore-runtime-config - secretRef: name: actcore-runtime-secret image: forgejo.coulomb.social/coulomb/activity-core@sha256:713bddad10a41950f446100a8b370fca8ccdd0b8969cccae751e3870c9c63ccd imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 3 httpGet: path: /health port: http scheme: HTTP initialDelaySeconds: 45 periodSeconds: 20 successThreshold: 1 timeoutSeconds: 5 name: api ports: - containerPort: 8010 name: http protocol: TCP readinessProbe: failureThreshold: 6 httpGet: path: /health port: http scheme: HTTP initialDelaySeconds: 10 periodSeconds: 10 successThreshold: 1 timeoutSeconds: 5 resources: {} terminationMessagePath: /dev/termination-log terminationMessagePolicy: File volumeMounts: - mountPath: /etc/activity-core/external-definitions/activity-definitions name: external-activity-definitions readOnly: true dnsPolicy: ClusterFirst restartPolicy: Always schedulerName: default-scheduler securityContext: {} terminationGracePeriodSeconds: 30 volumes: - configMap: defaultMode: 420 name: actcore-external-activity-definitions name: external-activity-definitions --- apiVersion: v1 kind: Service metadata: name: actcore-worker-metrics namespace: activity-core labels: app.kubernetes.io/name: actcore-worker app.kubernetes.io/part-of: activity-core spec: ports: - name: metrics port: 9090 protocol: TCP targetPort: metrics selector: app.kubernetes.io/name: actcore-worker sessionAffinity: None type: ClusterIP --- apiVersion: apps/v1 kind: Deployment metadata: name: actcore-worker namespace: activity-core labels: app.kubernetes.io/name: actcore-worker app.kubernetes.io/part-of: activity-core spec: progressDeadlineSeconds: 600 replicas: 1 revisionHistoryLimit: 10 selector: matchLabels: app.kubernetes.io/name: actcore-worker strategy: rollingUpdate: maxSurge: 25% maxUnavailable: 25% type: RollingUpdate template: metadata: annotations: activity-core/retention-sha256: fa3d4cc8cdcc14ecce509cf6865c6e4726d722f84814b97d40686d68d6ee5bb1 kubectl.kubernetes.io/restartedAt: '2026-09-05T20:48:57+02:00' labels: app.kubernetes.io/name: actcore-worker app.kubernetes.io/part-of: activity-core spec: containers: - command: - python - -m - activity_core.worker env: - name: ISSUE_SINK_TYPE value: state-hub - name: KUBECONFIG_R01 value: /kube/config-hosteurope - name: KUBECONFIG_CORE value: /kube/config envFrom: - configMapRef: name: actcore-runtime-config - secretRef: name: actcore-runtime-secret image: forgejo.coulomb.social/coulomb/activity-core@sha256:713bddad10a41950f446100a8b370fca8ccdd0b8969cccae751e3870c9c63ccd imagePullPolicy: IfNotPresent name: worker ports: - containerPort: 9090 name: metrics protocol: TCP resources: {} terminationMessagePath: /dev/termination-log terminationMessagePolicy: File volumeMounts: - mountPath: /opt/railiance-backup-verified name: backup-verified readOnly: true - mountPath: /opt/railiance-platform/tools/cmd/forgejo-backup name: backup-verified readOnly: true subPath: entrypoint - mountPath: /etc/activity-core/external-definitions/activity-definitions name: external-activity-definitions readOnly: true - mountPath: /etc/activity-core/schemas name: report-schemas readOnly: true - mountPath: /etc/activity-core/ops name: ops-service-inventory readOnly: true - mountPath: /var/custodian/memory/working name: working-memory - mountPath: /var/custodian/runtime/prompts name: custodian-runtime readOnly: true - mountPath: /opt/railiance-platform/scripts/forgejo_package_prune.py name: ops-service-inventory subPath: forgejo_package_prune.py readOnly: true - mountPath: /opt/railiance-platform name: railiance-platform readOnly: true - mountPath: /var/lib/railiance-platform/live-images name: live-image-inventory readOnly: true - mountPath: /kube name: kubeconfigs readOnly: true dnsPolicy: ClusterFirst restartPolicy: Always schedulerName: default-scheduler securityContext: fsGroup: 1000 runAsGroup: 1000 runAsUser: 1000 terminationGracePeriodSeconds: 30 volumes: - configMap: defaultMode: 365 items: - key: tools__cmd__forgejo-backup path: tools/cmd/forgejo-backup - key: scripts__capture_forgejo_archive.py path: scripts/capture_forgejo_archive.py - key: lib__railiance-backup-common.sh path: lib/railiance-backup-common.sh - key: lib__railiance-print.sh path: lib/railiance-print.sh - key: entrypoint path: entrypoint name: backup-verified-0220ca56520c name: backup-verified - configMap: defaultMode: 420 name: actcore-external-activity-definitions name: external-activity-definitions - configMap: defaultMode: 420 name: actcore-report-schemas name: report-schemas - configMap: defaultMode: 420 name: actcore-ops-service-inventory name: ops-service-inventory - hostPath: path: /home/tegwick/the-custodian/memory/working type: DirectoryOrCreate name: working-memory - configMap: defaultMode: 420 name: actcore-custodian-runtime name: custodian-runtime - hostPath: path: /home/tegwick/railiance-platform type: Directory name: railiance-platform - hostPath: path: /home/tegwick/.local/state/railiance-platform/live-images type: Directory name: live-image-inventory - hostPath: path: /home/tegwick/.kube type: Directory name: kubeconfigs --- apiVersion: apps/v1 kind: Deployment metadata: name: actcore-event-router namespace: activity-core labels: app.kubernetes.io/name: actcore-event-router app.kubernetes.io/part-of: activity-core spec: progressDeadlineSeconds: 600 replicas: 1 revisionHistoryLimit: 10 selector: matchLabels: app.kubernetes.io/name: actcore-event-router strategy: rollingUpdate: maxSurge: 25% maxUnavailable: 25% type: RollingUpdate template: metadata: annotations: kubectl.kubernetes.io/restartedAt: '2026-09-05T20:48:55+02:00' labels: app.kubernetes.io/name: actcore-event-router app.kubernetes.io/part-of: activity-core spec: containers: - command: - python - -m - activity_core.event_router env: - name: ISSUE_SINK_TYPE value: state-hub envFrom: - configMapRef: name: actcore-runtime-config - secretRef: name: actcore-runtime-secret image: forgejo.coulomb.social/coulomb/activity-core@sha256:cd4e924c2809f0d1d319e53a9ddd20db2e88a1b543d62e6442702c184b7842f4 imagePullPolicy: IfNotPresent name: event-router resources: {} terminationMessagePath: /dev/termination-log terminationMessagePolicy: File dnsPolicy: ClusterFirst restartPolicy: Always schedulerName: default-scheduler securityContext: {} terminationGracePeriodSeconds: 30