the-custodian/docs/weekly-allocation-report.md
codex 7dc1133f4c CUST-WP-0071-T05: in-cluster weekly allocation runner (source only)
Stdlib-only CronJob entry point that reads the Kubernetes API (get/list on
nodes, pods, PVCs, PVs, storageclasses, metrics.k8s.io) and Prometheus, reuses
build_observation / reconcile.join / build_report / retain unchanged, and
retains ISO-week reports, per-attempt inputs and an append-only receipt log.
Telemetry window ends Monday 06:00 UTC so weeks stay comparable across DST.
Reconcile now imports PyYAML only in its CLI. Not deployed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 08:13:07 +02:00

153 lines
8.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Retained weekly allocation assessment
`scripts/weekly_allocation_report.py` implements the offline evidence join for
CUST-WP-0071-T05. It reads the existing cluster collector, Custodian allocation
reconcile, Prometheus query-response capture and resource-control inventory.
It performs no collection, deployment, network calls, task writes or delivery.
It is suitable for an accepted durable executor; it does not install one.
## Inputs and retained evidence
The required inputs are:
- `--cluster`: `railiance.cluster-resource-observation.v1`, produced by
railiance-cluster's `make cluster-observe REMOTE=railiance01`.
- `--allocation`: `custodian.allocation-reconcile.v1` from
`scripts/reconcile_railiance_allocation.py`, matching the same capture and
workload inventory. Run both against the same retained observation.
- `--telemetry`: the existing September 28 query-capture shape: `captured_at`,
`window: 7d`, `step: 5m`, and `queries` mapping each metric name to its exact
`promql` and Prometheus `response`. Preserve original sample evaluation times;
do not restamp historical query results. These describe the query window,
not independently verified raw scrape completeness.
- `--inventory`: resource-control's `data/resources/reef-railiance-k3s.json`.
Dated portfolio capacity is retained as portfolio context, never summed with
the live cluster or treated as current scheduler evidence.
- `--provenance`: exact source collector revisions and `source_observation`
matching the cluster capture, as in the September 28 allocation provenance.
- `--as-of`: explicit evaluation timestamp; identical retries use the same value.
- `--output`: a new retained JSON path. Exact identical retries succeed; changed
content at that path fails and requires an explicit revision path. Writes use
a complete temporary file and atomic create-without-replacement publication.
Optional `--previous` selects the previous retained report. Deltas require the
same resource, exact query texts, sample step, seven-day window and consecutive
capture endpoints within one sample step. Other comparisons stay unknown; the
report still records disappeared workloads. Recommendation identity depends on
resource/namespace/workload/review purpose, not the date or recommendation text.
Existing receiving-task links survive retries and subsequent reports.
Metric names are `cpu_p95_m`, `cpu_peak_m`, `memory_p95_bytes`,
`memory_peak_bytes`, `cpu_throttle_ratio`, `latency_p95_seconds`, `errors`,
`request_count`, `restarts`, `oom_events`, `cpu_sample_count` and
`memory_sample_count`. Metrics are nonnegative, finite instant-vector aggregates.
Workload queries must carry exactly `namespace` and `workload` identity labels
(an optional Prometheus `__name__` is allowed). Extra labels or duplicate series
are not silently aggregated. Namespace-only results are retained separately;
they never become per-workload values, even for a currently single-workload
namespace. Namespace peaks and percentiles are not additive.
Missing series, failed queries and non-finite values remain null with explicit
reasons. Observed zero remains zero. Evaluation counts never prove raw scrape
coverage. CPU/memory limits and instantaneous observations come from the cluster
snapshot; historical latency, request/errors, OOM or throttling are not inferred
from that snapshot. Unknown operating margin remains null; reservation residual
does not establish processing capacity or release acceptance.
## Owner decisions
Without a complete current owner review the result is `investigate`, with low
confidence and explicit evidence gaps. `--reviews` accepts a JSON mapping keyed
by the stable recommendation ID. A review provides `decision` (keep, increase,
decrease or investigate), `owner`, `period_end` matching telemetry capture,
`reviewed_at`, `evidence_refs`, `receiving_task`, `configuration`, `rollback_ref`,
`coverage_ref`, `service_acceptance_ref`, and `margin_ref`. Optional `outcome`
records what happened to the preceding recommendation. These references are
owner assertions to be checked during acceptance, not proof generated by this
adapter. Wrong-owner/window reviews, missing workload metrics, zero CPU requests,
unknown owners and stale/future evidence keep the result at investigate and
retain the proposal for review. No idle sample automatically recommends a cut.
Only new observation evidence and verified owner disposition can complete T02–T04.
In particular, the historical Vergabe sample does not establish the attended
two-user p95/error acceptance target or incremental database attribution.
## Disabled execution contract
The owner-source candidate is
`activity-core/activity-definitions/weekly-railiance-allocation-review.md`.
Its fixed command is:
```sh
python3 scripts/weekly_allocation_report.py --request config/weekly-allocation-request.json
```
The request is intentionally unbound and the command fails until the accepted
executor supplies real input paths, a fixed `as_of`, and a durable output path.
Paths resolve relative to the request file. The executor must snapshot this
request per run, keep it stable for retries, bind a real previous report, and
retain reports beyond the seven-day raw-metric retention. The committed null
template is not a production storage location or collection implementation.
Activity Core source `4ae00fc` selects its existing ADR-007 bounded-operation
executor with fixed owner commands; both definitions remain disabled. The prior
coding profile cannot run Python or reach the Hub. Runtime owners must still
bind accepted source/fleet mounts, separate private retained state and scoped
network access. Activity Core owns the Monday 08:00 Europe/Berlin schedule and
bounded-activity/context/progress-sink evidence, with one Temporal attempt.
The full binding contract is `activity-core/docs/custodian-bounded-operations.md`.
The adapter's matching recommendation IDs are the downstream upsert keys for
existing owner work records; it does not itself create those records. Delivery,
explicit owner acknowledgment, independent missed/failed-run detection and
recovery remain unbound. A report file or completed bounded activity alone proves none
of them. T05 needs the first unattended schedule receipt and exercised recovery.
## Verification
```sh
python3 -m unittest discover -s tests -p test_weekly_allocation_report.py -v
```
The tests cover historical namespace-only input, missing/zero/NaN, invalid
aggregation, query failure, compatible/incompatible periods, stable identities
and owner-task links, stale/future evidence, owner review, resource/capture
mismatch and retained retry/collision behavior.
## In-cluster runner (CUST-WP-0071-T05, founder decision 2026-10-21)
`docs/decisions/2026-10-21-cust-wp-0071-delivery-interview.md` selects an
in-cluster Kubernetes CronJob for T05 and supersedes the Activity Core
placement above. `scripts/weekly_allocation_incluster.py` is the job entry
point. It is standard-library only and reuses the existing owner code
unchanged: railiance-cluster `build_observation`, `reconcile.join` (PyYAML is
now imported only by the reconcile CLI) and this adapter's `build_report` and
`retain`.
- Reads: the Kubernetes API through the pod ServiceAccount, with get/list on
nodes, pods, persistentvolumeclaims, persistentvolumes, storageclasses and
metrics.k8s.io nodes/pods only. Prometheus is reached through
`PROMETHEUS_URL` with `config/weekly-allocation-queries.json`.
- Week identity: the ISO week of (run time - 6h). The telemetry window ends at
Monday 06:00 UTC, so consecutive windows are exactly seven days apart in CET
and CEST. Prometheus keeps seven days of data, so the run must happen on the Monday. A
2026-10-21 (Wednesday) read-only dry run against railiance01 retained the
report, but per-namespace CPU sample counts reached at most 1512 of 2016,
because the window start had already expired.
- Retention: `<data>/reports/<YYYY>-W<ww>.json` (create-without-replacement),
per-attempt inputs under `<data>/runs/<week>/<attempt>/`, and an append-only
`<data>/receipts.jsonl` with status `retained`, `already-retained` or
`failed`, previous week, `missed_weeks`, comparability and failed queries.
- Idempotency: a retained week is a no-op success. A failed attempt leaves no
report, so the Job retry or a manual run collects again.
- Not done: delivery, owner acknowledgment and owner-task creation
(`delivery: not emitted`). Workload rows stay `investigate` because the queries
are namespace-level. kube-state-metrics does not export `kube_pod_labels`, so
no workload-labelled series exist yet.
Executor manifests (suspended, not registered with ArgoCD) live in
railiance-platform `argocd/platform-addons/custodian-allocation-review/`, with
the draft Application in `argocd/railiance01/drafts/`.
```sh
python3 -m pytest tests/test_weekly_allocation_incluster.py tests/test_weekly_allocation_report.py -q
```