diff --git a/workplans/ACTIVITY-WP-0020-weekly-forgejo-package-prune.md b/workplans/ACTIVITY-WP-0020-weekly-forgejo-package-prune.md new file mode 100644 index 0000000..d6534d6 --- /dev/null +++ b/workplans/ACTIVITY-WP-0020-weekly-forgejo-package-prune.md @@ -0,0 +1,263 @@ +--- +id: ACTIVITY-WP-0020 +type: workplan +title: "Weekly Forgejo package prune (retain 3 versions)" +domain: infotech +repo: activity-core +status: proposed +owner: codex +topic_slug: activity-core +created: "2026-07-12" +updated: "2026-07-12" +state_hub_workstream_id: "33a5b5e0-310c-442e-a29f-902227b9d7e1" +--- + +# Weekly Forgejo package prune (retain 3 versions) + +## Problem + +Forgejo `forgejo dump` backups are dominated by **OCI container image layers** +under `data/packages` (~80% of blob zip; ~1.3 GB today). Growth is ~686 MiB +(Jul 7) → ~1.0 GiB (Jul 9) → ~1.3 GiB (Jul 12) because CI pushes new image tags +on every deploy and **no automated prune** runs yet +(`railiance-apps/docs/forgejo-package-registry.md`). + +Manual UI deletion does not scale. Prune must be policy-driven, dry-run first, +and emit non-secret evidence for backup/ops gates. + +## Goal + +Establish a **retention depth of 3 versions per package** (newest three kept; +older versions deleted) across Forgejo package types that affect backup size, and +run pruning **weekly via activity-core** on railiance01. + +## Retention policy (canonical) + +| Rule | Value | +| --- | --- | +| Versions kept per package | **3** (newest by `created_at`; tie-break on version string) | +| Package types in scope (phase 1) | `container` (OCI), `pypi`, `npm`, `generic` | +| Protected versions | Any tag/version referenced by **production deploy manifests** in `railiance-apps` Helm values / chart defaults (never delete even if outside top 3) | +| Org scope | `coulomb` | +| Default mode | **dry-run**; deletes only with explicit `--apply` | +| Evidence | Non-secret summary posted to State Hub (`forgejo_package_prune`) | + +Phase 2 (out of scope here): Helm/Maven/Go registries when consumer inventory +proves need. + +## Architecture + +``` +activity-core (weekly cron) + → shell context resolver: forgejo_package_prune + → railiance-platform: tools/cmd/forgejo-package-prune --apply + → Forgejo API DELETE /api/v1/packages/{owner}/{type}/{name}/{version} + → State Hub progress evidence (counts, bytes estimate, errors; no tokens) +``` + +**Credential routing:** Forgejo PAT with `read:package` + `write:package` from +OpenBao via `warden route find "forgejo package"` — never stored in Git, +workplans, or activity-definition files. Runtime token file path only. + +**Schedule:** Sunday **03:30 UTC** — after daily `make forgejo-backup` cron +(02:15 UTC) so a fresh backup exists before destructive prune. + +## Scope boundary + +| In scope | Out of scope | +| --- | --- | +| Prune script + Make target in `railiance-platform` | Rewriting `forgejo-backup` chunk copy | +| Weekly ActivityDefinition in `activity-core` | Gitea legacy registry cleanup | +| Retention docs in `railiance-apps` + `railiance-platform` | SBOM signing, vulnerability scanning | +| Production-tag protection from Helm inventory | Cross-org packages outside `coulomb` | +| Dry-run + first apply evidence | Auto-prune git `repos/` or `repo-archive` blobs | + +## Dependencies + +- Forgejo API reachable (`forgejo.coulomb.social`) +- Valid Forgejo operator/service PAT lane (OpenBao) +- `activity-core` worker on railiance01 with `ACTIVITY_DEFINITION_DIRS` including + repo definitions and network egress to Forgejo +- `make sync-schedules` / worker startup schedule reconciliation + +## Tasks + +### T01 — Document retention policy and operator guardrails + +```task +id: ACTIVITY-WP-0020-T01 +status: todo +priority: high +state_hub_task_id: "d4212ce4-9292-4c56-abcd-952e0c1bbab7" +``` + +Add canonical retention prose to: + +- `railiance-platform/docs/forgejo-package-prune.md` (new) +- `railiance-apps/docs/forgejo-package-registry.md` § Retention (replace + "no auto-prune yet") + +Cover: depth-3 rule, protected production tags, dry-run default, weekly +activity-core schedule, evidence event type, and rollback posture (restore from +Nextcloud dump if a needed tag was removed). + +Acceptance: operator can answer "what gets deleted, what is protected, how often" +without reading source. + +--- + +### T02 — Implement `forgejo-package-prune` command + +```task +id: ACTIVITY-WP-0020-T02 +status: todo +priority: high +state_hub_task_id: "3abec46e-fca8-4583-b681-c0836e0bf666" +``` + +In `railiance-platform`: + +- `tools/cmd/forgejo-package-prune` — list packages for owner `coulomb`, group + versions, compute deletions beyond newest 3, subtract protected tags from Helm + inventory scan (`railiance-apps/helm/*-values.yaml`, + `railiance-apps/charts/*/values.yaml` image tags). +- Flags: `--dry-run` (default), `--apply`, `--max-versions 3`, `--types + container,pypi,npm,generic`, `--owner coulomb`. +- Auth: `FORGEJO_TOKEN` or OpenBao field via existing backup-lane pattern; + preflight `401` → clear error. +- Output: JSON summary on stdout for activity-core parsing + (`deleted`, `skipped_protected`, `would_delete`, `errors[]`); human lines via + `railiance-print.sh`. +- `Makefile` targets: `forgejo-package-prune-dry-run`, `forgejo-package-prune`. + +Acceptance: dry-run against production lists candidates; no deletes without +`--apply`; unit or fixture tests for version sorting and protection logic. + +--- + +### T03 — Register shell resolver query `forgejo_package_prune` + +```task +id: ACTIVITY-WP-0020-T03 +status: todo +priority: high +state_hub_task_id: "f8c6eb1a-2682-49b0-a6ab-e96c8174f75f" +``` + +In `activity-core`, extend the `shell` context resolver (same pattern as +`reuse_surface_report_gaps` in `context_resolvers/reuse_surface.py`): + +- Query: `forgejo_package_prune` +- Params: `prune_script` (default + `~/railiance-platform/tools/cmd/forgejo-package-prune`), `apply` (bool, default + `false` for first ship), `max_versions`, `timeout_seconds` +- Invoke script with bounded timeout; parse JSON stdout into `context.prune` +- Never capture or persist token env vars in context/working memory + +Acceptance: resolver tests with mocked subprocess JSON; failure surfaces non-zero +exit and stderr snippet (truncated, no secrets). + +--- + +### T04 — Add weekly ActivityDefinition + +```task +id: ACTIVITY-WP-0020-T04 +status: todo +priority: high +state_hub_task_id: "2da2ca5d-1c59-4835-b48e-7bf3bfae97b4" +``` + +Add `activity-definitions/weekly-forgejo-package-prune.md`: + +```yaml +trigger: + type: cron + cron_expression: "30 3 * * 0" + timezone: UTC + misfire_policy: skip +context_sources: + - type: shell + query: forgejo_package_prune + params: + apply: true + max_versions: 3 + evidence_sinks: + - type: state-hub-progress + event_type: forgejo_package_prune + author: activity-core + bind_to: context.prune +``` + +Ship `enabled: false` until dry-run evidence exists. No rules / task emission — +observational prune + evidence only (same shape as `weekly-legacy-meter-review`). + +Acceptance: definition parses via `sync-definitions`; schedule id derivable from +`make automation-inventory`. + +--- + +### T05 — Dry-run verify, enable, and runbook + +```task +id: ACTIVITY-WP-0020-T05 +status: todo +priority: medium +state_hub_task_id: "debc74f0-d43d-401e-b65f-c6dbb5b399d7" +``` + +1. Run resolver smoke with `apply: false`; capture candidate counts vs Jul 12 + backup analysis (~800 MiB packages). +2. Operator review of dry-run output; confirm protected tags (e.g. + `vergabe-teilnahme:064d295`) never appear in `would_delete`. +3. Flip `enabled: true`; `make sync-schedules` on railiance01 worker. +4. Document in `activity-core/docs/runbook.md` alongside + `weekly-sbom-staleness` and `weekly-legacy-meter-review`. + +Acceptance: one successful dry-run workflow in Temporal; schedule visible in +automation inventory; runbook lists cron, credential pointer, and rollback. + +--- + +### T06 — First apply run and backup impact evidence + +```task +id: ACTIVITY-WP-0020-T06 +status: todo +priority: medium +state_hub_task_id: "600c7b22-9278-430b-9cba-902acb5c279a" +``` + +After T05 approval, run one manual `make forgejo-package-prune` with `--apply` +(or trigger workflow once with `apply: true`). Then: + +1. Post `forgejo_package_prune` progress to State Hub (deleted counts, errors). +2. Run `make forgejo-backup-dry-run` and record dump size delta vs pre-prune + baseline in `railiance-platform/docs/evidence/forgejo-package-prune-YYYYMMDD.json`. +3. Update `railiance-apps/docs/forgejo-package-registry.md` retention table with + automated prune row. + +Acceptance: evidence file exists; next backup blob zip measurably smaller or +stable; no production deploy tag removed (smoke: `crictl pull` or helm image +still present). + +--- + +## Registration + +After approval, register in State Hub: + +```bash +statehub fix-consistency --repo activity-core +``` + +Cross-repo implementation commits land in `railiance-platform` and +`railiance-apps`; link them in T06 evidence — no separate hub workplan required +unless scope expands. + +## Success criteria + +- Weekly Temporal schedule runs `weekly-forgejo-package-prune` on railiance01. +- Each package under `coulomb` retains at most **3** versions unless protected. +- State Hub receives `forgejo_package_prune` evidence every run. +- `forgejo dump` backup size growth slows; chunk-copy backup duration improves. \ No newline at end of file