Publish CSOC-WP-0005 resource demand and cost evidence

Add low/base/high demand forecasts, service objectives with timestamped
observations, and workload operations labor for resource:tenant:coulomb:coulomb-social.
This commit is contained in:
tegwick 2026-08-12 11:07:03 +02:00
parent b8154b8e7d
commit c521adc2f9
10 changed files with 667 additions and 14 deletions

View file

@ -78,7 +78,8 @@ The rebuild is deliberately **product-faithful first**: UI and content parity ma
- `docs/adr/ADR-0002-space-content-forgejo-markdown.md` — space content as markdown in Forgejo
- `docs/deploy.md` — deploy + spaces operator runbook
- `workplans/CSOC-WP-0001-bubble-io-exit-assessment.md` — Bubble inventory/migration **after** product foundation
- `workplans/CSOC-WP-0005-resource-demand-and-cost-evidence.md` — resource evidence for platform cost control
- `workplans/CSOC-WP-0005-resource-demand-and-cost-evidence.md`**finished**: demand/SLO/labor evidence for resource-control
- `docs/resource-evidence/` — workload demand forecasts, observations, labor
- `the-custodian/docs/coulomb-social-rebuild-seed.md` — original workplan seed (CUST-WP-0058-T08)
- `the-custodian/canon/standards/business-app-service-contract_v0.1.md`
- NetKingdom: IAM Profile, user-engine boundary, KeyCape/Keycloak modes

View file

@ -72,7 +72,8 @@ coulomb.social connects people around shared projects and complementary capabili
- Finished identity path: **`CSOC-WP-0002`**, **`CSOC-WP-0003`** (Case A; Case B residual intakes)
- Content ADR: `docs/adr/ADR-0002-space-content-forgejo-markdown.md`
- Operator: `docs/deploy.md` runbook; residuals `docs/intakes/csoc-residuals.md`
- Open: bulk Bubble migration (`CSOC-WP-0001`); resource evidence (`CSOC-WP-0005`); public registration residuals (`CSOC-IN-0001`/`0002`, NK-WP-0025)
- Finished resource evidence: **`CSOC-WP-0005`** → `docs/resource-evidence/`
- Open: bulk Bubble migration (`CSOC-WP-0001`); public registration residuals (`CSOC-IN-0001`/`0002`, NK-WP-0025)
---

View file

@ -12,7 +12,7 @@
| workplan | CSOC-WP-0002 | done | — | workplans/CSOC-WP-0002-netkingdom-user-management-reestablish.md |
| workplan | CSOC-WP-0003 | finished | — | workplans/CSOC-WP-0003-self-registration-and-assurance.md |
| workplan | CSOC-WP-0004 | finished | — | workplans/CSOC-WP-0004-app-shell-and-space-content.md |
| workplan | CSOC-WP-0005 | ready | — | workplans/CSOC-WP-0005-resource-demand-and-cost-evidence.md |
| workplan | CSOC-WP-0005 | finished | — | workplans/CSOC-WP-0005-resource-demand-and-cost-evidence.md |
| task | CSOC-WP-0001-T01 | todo | — | workplans/CSOC-WP-0001-bubble-io-exit-assessment.md |
| task | CSOC-WP-0001-T02 | todo | — | workplans/CSOC-WP-0001-bubble-io-exit-assessment.md |
| task | CSOC-WP-0001-T03 | wait | — | workplans/CSOC-WP-0001-bubble-io-exit-assessment.md |
@ -36,8 +36,8 @@
| task | CSOC-WP-0004-T05 | done | — | workplans/CSOC-WP-0004-app-shell-and-space-content.md |
| task | CSOC-WP-0004-T06 | done | — | workplans/CSOC-WP-0004-app-shell-and-space-content.md |
| task | CSOC-WP-0004-T07 | done | — | workplans/CSOC-WP-0004-app-shell-and-space-content.md |
| task | CSOC-WP-0005-T01 | todo | — | workplans/CSOC-WP-0005-resource-demand-and-cost-evidence.md |
| task | CSOC-WP-0005-T02 | todo | — | workplans/CSOC-WP-0005-resource-demand-and-cost-evidence.md |
| task | CSOC-WP-0005-T03 | todo | — | workplans/CSOC-WP-0005-resource-demand-and-cost-evidence.md |
| task | CSOC-WP-0005-T01 | done | — | workplans/CSOC-WP-0005-resource-demand-and-cost-evidence.md |
| task | CSOC-WP-0005-T02 | done | — | workplans/CSOC-WP-0005-resource-demand-and-cost-evidence.md |
| task | CSOC-WP-0005-T03 | done | — | workplans/CSOC-WP-0005-resource-demand-and-cost-evidence.md |
| intake | CSOC-IN-0001 | open | blue | docs/intakes/csoc-residuals.md |
| intake | CSOC-IN-0002 | open | green | docs/intakes/csoc-residuals.md |

View file

@ -0,0 +1,30 @@
# Resource demand and cost evidence
Workload-owned evidence for
`resource:tenant:coulomb:coulomb-social` (CSOC-WP-0005).
**Authority boundary**
| Concern | Owner |
|---------|--------|
| Demand forecasts, app observations, service objectives, app labor | **this repo** |
| Cluster / host capacity and utilization | `railiance-cluster` / `railiance-infra` |
| Shared Postgres (`apps-pg`) | `railiance-platform` |
| Forgejo storage (space markdown SoR) | `railiance-forge` |
| Booked financial facts / invoices | `fin-hub` |
| Portfolio forecast join / optimization | `resource-control` |
Do **not** put secret values, provider credentials, or invoices here.
## Documents
| File | Task | Content |
|------|------|---------|
| `demand-forecast-2026-08.md` | T01 | Narrative + assumptions |
| `demand-forecast-2026-08.json` | T01 | Machine-readable low/base/high proxies |
| `service-objectives-and-observations-2026-08.md` | T02 | SLOs + timestamped observations |
| `service-objectives-and-observations-2026-08.json` | T02 | Stable units for join |
| `workload-operations-labor-2026-08.md` | T03 | Setup + recurring app labor |
Inventory pointer (resource-control):
`resource-control/data/resources/coulomb-social-production.json`.

View file

@ -0,0 +1,145 @@
{
"schema_version": "0.1",
"record_kind": "workload_demand_forecast",
"resource_id": "resource:tenant:coulomb:coulomb-social",
"service_id": "coulomb-social",
"workload_id": "coulomb-social",
"tenant_id": "tenant:coulomb",
"environment": "production",
"cost_attribution_key": "resource:tenant:coulomb:coulomb-social",
"currency": "EUR",
"forecast_version": "csoc-demand-2026-08-v1",
"created_at": "2026-08-12T09:03:47Z",
"horizon_months": 12,
"planning_period": {
"start": "2026-09-01",
"end": "2027-08-31"
},
"tenancy_model": {
"deployment_shape": "instance_per_client",
"this_record_scope": "single Coulomb reference instance on shared Railiance capacity",
"current_tenant": "tenant:coulomb",
"external_client_instances": "each future paying client is a separate deployment and its own resource id — not rolled into these scenarios"
},
"declared_capacity_floor": {
"replicas": 1,
"cpu_request_millicores": 100,
"cpu_limit_millicores": 1000,
"memory_request_mi": 256,
"memory_limit_mi": 1024,
"source": "railiance-apps/charts/coulomb-social/values.yaml"
},
"proxies": {
"units": {
"active_tenants": "count of tenant_id values with activity in period",
"active_members": "count of Member rows with session activity in period (proxy: monthly active)",
"http_requests_per_month": "application HTTP requests (ingress to app Service)",
"oidc_logins_per_month": "successful OIDC/dev login completions",
"background_jobs_per_month": "async/worker jobs (none in v1 runtime — reserved)",
"app_database_gb": "logical size of coulomb_social_db on apps-pg",
"app_database_growth_pct_per_month": "month-over-month logical DB growth",
"stored_media_gb": "app-owned binary media (PV or object store); Forgejo markdown is NOT counted here",
"forgejo_content_gb_bound": "markdown/assets bound for spaces of this tenant (shared Forgejo service; attribution only)",
"ingress_gb_per_month": "bytes into the app Service",
"egress_gb_per_month": "bytes out of the app pod (HTML + API responses; excludes Forgejo browser edits)",
"cpu_request_millicores": "declared Kubernetes request",
"memory_request_mi": "declared Kubernetes request",
"operator_hours_per_month": "workload-owned ops labor hours (see labor doc)"
}
},
"scenarios": {
"low": {
"label": "parallel-host operator use (current posture)",
"description": "app.coulomb.social live; apex remains Bubble; handful of operator accounts; no bulk member migration.",
"active_tenants": 1,
"active_members": 5,
"http_requests_per_month": 5000,
"oidc_logins_per_month": 80,
"background_jobs_per_month": 0,
"app_database_gb": 0.1,
"app_database_growth_pct_per_month": 5,
"stored_media_gb": 0,
"forgejo_content_gb_bound": 0.01,
"ingress_gb_per_month": 0.5,
"egress_gb_per_month": 1,
"cpu_request_millicores": 100,
"cpu_limit_millicores": 1000,
"memory_request_mi": 256,
"memory_limit_mi": 1024,
"replicas": 1,
"operator_hours_per_month": 2,
"operator_hourly_eur": 60
},
"base": {
"label": "Coulomb community active on rebuild",
"description": "Members and spaces actively used on app host; Bubble still may exist but primary day-to-day for Coulomb operators/members shifts to rebuild; still single tenant:coulomb instance.",
"active_tenants": 1,
"active_members": 50,
"http_requests_per_month": 150000,
"oidc_logins_per_month": 1500,
"background_jobs_per_month": 0,
"app_database_gb": 1,
"app_database_growth_pct_per_month": 10,
"stored_media_gb": 2,
"forgejo_content_gb_bound": 0.5,
"ingress_gb_per_month": 5,
"egress_gb_per_month": 15,
"cpu_request_millicores": 200,
"cpu_limit_millicores": 1000,
"memory_request_mi": 512,
"memory_limit_mi": 1024,
"replicas": 1,
"operator_hours_per_month": 4,
"operator_hourly_eur": 60
},
"high": {
"label": "full Bubble parity load on this Coulomb instance",
"description": "All Coulomb spaces/content migrated; peak community concurrency; optional second replica for HA; still NOT multi-client — external clients would be separate instances.",
"active_tenants": 1,
"active_members": 500,
"http_requests_per_month": 2000000,
"oidc_logins_per_month": 20000,
"background_jobs_per_month": 5000,
"app_database_gb": 10,
"app_database_growth_pct_per_month": 15,
"stored_media_gb": 50,
"forgejo_content_gb_bound": 20,
"ingress_gb_per_month": 40,
"egress_gb_per_month": 120,
"cpu_request_millicores": 500,
"cpu_limit_millicores": 2000,
"memory_request_mi": 1024,
"memory_limit_mi": 2048,
"replicas": 2,
"operator_hours_per_month": 8,
"operator_hourly_eur": 60
}
},
"monthly_labor_eur_proxy": {
"low": 120.0,
"base": 240.0,
"high": 480.0,
"formula": "operator_hours_per_month × operator_hourly_eur",
"note": "Infrastructure EUR is unknown here (allocated share of cluster/apps-pg/ingress/identity). Leave infrastructure null in cost joins until platform/fin-hub supplies figures."
},
"assumptions": [
"Scenarios are per-instance demand for tenant:coulomb, not fleet-wide multi-tenant aggregation.",
"Current production posture (2026-08) matches low: parallel host, identity + shell + spaces MVP, Bubble still on apex.",
"Space page bodies live in Forgejo (ADR-0002); forgejo_content_gb_bound is attribution input for shared Forgejo, not a claim that coulomb-social operates that storage.",
"App media PV is currently disabled in chart; stored_media_gb remains 0 until object storage or PV is enabled.",
"No application worker/queue in v1; background_jobs_per_month is 0 until a job system exists (high reserves headroom).",
"OIDC/identity platform traffic cost is not counted as app infrastructure; only app HTTP path is in http_requests_per_month.",
"Database is coulomb_social_db on shared apps-pg; logical size ownership is workload, physical volume ownership is railiance-platform.",
"CPU/memory figures are declared requirements for rightsizing; observed utilization is cluster authority when sampled.",
"Operator hourly rate €60 matches resource-control backup labor convention for comparability; not a payroll fact.",
"Falsifiable: after each calendar month, compare active_members, http_requests, app_database_gb, and operator_hours against the chosen scenario band."
],
"source_evidence": [
"docs/resource-evidence/demand-forecast-2026-08.md",
"docs/adr/ADR-0002-space-content-forgejo-markdown.md",
"docs/deploy.md",
"railiance-apps/charts/coulomb-social/values.yaml",
"resource-control/data/resources/coulomb-social-production.json",
"workplans/CSOC-WP-0005-resource-demand-and-cost-evidence.md"
]
}

View file

@ -0,0 +1,95 @@
# Workload demand forecast — coulomb-social (2026-08)
| Field | Value |
|-------|--------|
| Resource | `resource:tenant:coulomb:coulomb-social` |
| Workplan | CSOC-WP-0005-T01 |
| Forecast version | `csoc-demand-2026-08-v1` |
| Created | 2026-08-12T09:03:47Z |
| Machine record | `demand-forecast-2026-08.json` |
## Scope
Demand for **this production instance** (Coulomb reference tenant on
Railiance). Deployment shape is **instance-per-client** (DR-1 C): future
external customers are **separate instances** and separate resource ids —
they are **not** folded into the high scenario.
| Case | What it models |
|------|----------------|
| **low** | Current parallel-host posture: few operators, Bubble still full product on apex |
| **base** | Coulomb community day-to-day on the rebuild (still one tenant) |
| **high** | Full Bubble-parity load on this Coulomb instance (HA-ish replicas/resources) |
## Proxies (stable units)
| Proxy | Unit | Notes |
|-------|------|--------|
| active_tenants | count | Expected 1 for this instance |
| active_members | MAU-ish Member count | App-owned |
| http_requests_per_month | count | App Service ingress |
| oidc_logins_per_month | count | Successful app logins |
| background_jobs_per_month | count | 0 until workers exist |
| app_database_gb | GB logical | `coulomb_social_db` on apps-pg |
| stored_media_gb | GB | App-owned binaries only |
| forgejo_content_gb_bound | GB | Attribution for space markdown on shared Forgejo |
| ingress_gb / egress_gb | GB/month | App path only |
| cpu/memory request·limit | mCPU / Mi | Declared K8s requirements |
| operator_hours_per_month | hours | Workload labor (T03) |
## Scenario table (monthly)
| Proxy | low | base | high |
|-------|----:|-----:|-----:|
| active_tenants | 1 | 1 | 1 |
| active_members | 5 | 50 | 500 |
| http_requests | 5000 | 150000 | 2000000 |
| oidc_logins | 80 | 1500 | 20000 |
| background_jobs | 0 | 0 | 5000 |
| app_database_gb | 0.1 | 1 | 10 |
| DB growth %/mo | 5 | 10 | 15 |
| stored_media_gb | 0 | 2 | 50 |
| forgejo_content_gb_bound | 0.01 | 0.5 | 20 |
| ingress_gb | 0.5 | 5 | 40 |
| egress_gb | 1 | 15 | 120 |
| cpu request (m) | 100 | 200 | 500 |
| mem request (Mi) | 256 | 512 | 1024 |
| replicas | 1 | 1 | 2 |
| operator h/mo | 2 | 4 | 8 |
| labor €/mo @ €60/h | 120 | 240 | 480 |
Infrastructure EUR is **not invented** here. Cost joins should treat
infrastructure as an allocated share of cluster, apps-pg, ingress, and
identity platform once those owners and fin-hub supply figures.
## Capacity floor (current deploy declaration)
From `railiance-apps` chart defaults (production values do not override
resources as of 2026-08):
| Dimension | Value |
|-----------|--------|
| replicas | 1 |
| cpu request / limit | 100m / 1000m |
| memory request / limit | 256Mi / 1Gi |
| media PV | disabled |
| Image tag (values) | `7fcd0cf` (railiance-apps helm values; may lag git HEAD) |
## How resource-control should use this
1. Pick scenario band from observed MAU / requests (or default **low** until
Bubble migration starts).
2. Feed proxies into monthly forecast/actual control (null infrastructure
until platform prices exist).
3. Attribute Forgejo-bound content via `forgejo_content_gb_bound` without
double-counting storage in the app resource.
4. Recalibrate after three comparable months (MAPE on members, requests, DB
GB, labor hours).
## Assumptions and falsifiability
See JSON `assumptions` array. Primary falsifiers after each calendar month:
- measured `active_members` outside the active scenario band by >2×
- `app_database_gb` growth vs declared growth %
- operator hours vs T03 labor log

View file

@ -0,0 +1,210 @@
{
"schema_version": "0.1",
"record_kind": "service_objectives_and_usage_observations",
"resource_id": "resource:tenant:coulomb:coulomb-social",
"service_id": "coulomb-social",
"workload_id": "coulomb-social",
"tenant_id": "tenant:coulomb",
"environment": "production",
"cost_attribution_key": "resource:tenant:coulomb:coulomb-social",
"created_at": "2026-08-12T09:03:47Z",
"service_objectives": {
"availability": {
"target": "99.5_percent_monthly",
"scope": "HTTPS app.coulomb.social application responses excluding scheduled maintenance and dependency outages (OIDC, apps-pg, cluster)",
"measurement": "synthetic GET /healthz success ratio + ingress 5xx from platform telemetry when available",
"notes": "Not a contractual SLA; planning objective for infrastructure options."
},
"latency": {
"healthz_p99_ms": 500,
"authenticated_html_p95_ms": 1500,
"space_markdown_render_p95_ms": 3000,
"notes": "Markdown path includes Forgejo fetch; cold cache may exceed target — track separately."
},
"recovery": {
"rpo_minutes": 60,
"rto_minutes": 240,
"notes": "RPO/RTO for app DB via apps-pg backup ownership; content RPO/RTO inherits Forgejo backup policy for bound repos."
},
"retention": {
"app_database_backup_days": 30,
"application_logs_days": 14,
"session_cookie": "browser session; no long-lived app refresh tokens in v1",
"notes": "Backup retention executed by apps-pg/platform; app requires ≥30 days logical recoverability."
},
"tenant_isolation": {
"model": "tenant_id column isolation within instance + instance_per_client deployments",
"default_tenant": "tenant:coulomb",
"cross_tenant_list": "forbidden — Space queries filter by session tenant_id",
"identity": "NetKingdom OIDC (issuer, subject) Member binding; no local passwords"
},
"security": {
"authn": "OIDC public client + session cookies Secure/HttpOnly/SameSite=Lax in prod",
"secrets": "K8s Secret coulomb-social-env / OpenBao; never in git",
"content_xss": "bleach-sanitized markdown HTML"
}
},
"observations": [
{
"name": "public_healthz",
"value": "ok",
"unit": "status",
"observed_at": "2026-08-12T09:03:47Z",
"method": "curl -fsS https://app.coulomb.social/healthz",
"authority": "coulomb-social"
},
{
"name": "public_landing_http_status",
"value": "200",
"unit": "http_status",
"observed_at": "2026-08-12T09:03:47Z",
"method": "curl -o /dev/null -w status https://app.coulomb.social/",
"authority": "coulomb-social"
},
{
"name": "demo_fixture_raw_bytes",
"value": "586",
"unit": "bytes",
"observed_at": "2026-08-12T09:03:47Z",
"method": "GET Forgejo raw docs/space-fixtures/demo/pages/index.md",
"authority": "coulomb-social + forgejo.coulomb.social public raw"
},
{
"name": "declared_replicas",
"value": "1",
"unit": "count",
"observed_at": "2026-08-12",
"method": "chart values replicaCount",
"authority": "railiance-apps/charts/coulomb-social/values.yaml"
},
{
"name": "declared_cpu_request_millicores",
"value": "100",
"unit": "millicores",
"observed_at": "2026-08-12",
"method": "chart resources.requests.cpu",
"authority": "railiance-apps"
},
{
"name": "declared_memory_request_mi",
"value": "256",
"unit": "Mi",
"observed_at": "2026-08-12",
"method": "chart resources.requests.memory",
"authority": "railiance-apps"
},
{
"name": "declared_cpu_limit_millicores",
"value": "1000",
"unit": "millicores",
"observed_at": "2026-08-12",
"method": "chart resources.limits.cpu",
"authority": "railiance-apps"
},
{
"name": "declared_memory_limit_mi",
"value": "1024",
"unit": "Mi",
"observed_at": "2026-08-12",
"method": "chart resources.limits.memory",
"authority": "railiance-apps"
},
{
"name": "media_persistence_enabled",
"value": "false",
"unit": "bool",
"observed_at": "2026-08-12",
"method": "chart persistence.media.enabled",
"authority": "railiance-apps"
},
{
"name": "helm_image_tag",
"value": "7fcd0cf",
"unit": "git_sha_short",
"observed_at": "2026-08-12",
"method": "helm/coulomb-social-values.yaml image.tag",
"authority": "railiance-apps"
},
{
"name": "portfolio_ready_replicas",
"value": "1",
"unit": "count",
"observed_at": "2026-08-11",
"method": "Kubernetes API sample in RESOURCE-WP-0003 initial portfolio discovery",
"authority": "resource-control discovery (reef-railiance)"
},
{
"name": "commissioned_on",
"value": "2026-08-09",
"unit": "date",
"observed_at": "2026-08-11",
"method": "inventory record lifecycle.commissioned_on",
"authority": "resource-control/data/resources/coulomb-social-production.json"
},
{
"name": "app_database_gb_logical",
"value": "unknown",
"unit": "GB",
"observed_at": "2026-08-12",
"method": "not measured this session (kubectl/apps-pg access unavailable)",
"authority": "coulomb-social — gap; platform may supply"
},
{
"name": "production_member_count",
"value": "unknown",
"unit": "count",
"observed_at": "2026-08-12",
"method": "not queried (no production DB shell this session)",
"authority": "coulomb-social — gap"
},
{
"name": "observed_cpu_millicores",
"value": "unknown",
"unit": "millicores",
"observed_at": "2026-08-12",
"method": "metrics-server sample unavailable this session",
"authority": "railiance-cluster when sampled"
},
{
"name": "observed_memory_mi",
"value": "unknown",
"unit": "Mi",
"observed_at": "2026-08-12",
"method": "metrics-server sample unavailable this session",
"authority": "railiance-cluster when sampled"
},
{
"name": "local_dev_member_count",
"value": "2",
"unit": "count",
"observed_at": "2026-08-12T09:03:47Z",
"method": "local sqlite Member.objects.count() after pytest/dev use — not production",
"authority": "coulomb-social workstation (non-prod)"
}
],
"dependency_slo_inheritance": [
{
"dependency": "resource:railiance:apps-pg",
"inherits": ["database backup", "DB RPO/RTO physical path"]
},
{
"dependency": "resource:railiance:reef-railiance:k3s",
"inherits": ["node availability", "pod scheduling"]
},
{
"dependency": "resource:railiance:forgejo",
"inherits": ["space markdown durability", "git history"]
},
{
"dependency": "NetKingdom KeyCape / Authelia",
"inherits": ["authentication availability", "MFA path"]
}
],
"source_evidence": [
"docs/resource-evidence/service-objectives-and-observations-2026-08.md",
"docs/deploy.md",
"docs/identity/smoke.md",
"docs/adr/ADR-0001-netkingdom-identity.md",
"docs/adr/ADR-0002-space-content-forgejo-markdown.md"
]
}

View file

@ -0,0 +1,72 @@
# Service objectives and usage observations — coulomb-social (2026-08)
| Field | Value |
|-------|--------|
| Resource | `resource:tenant:coulomb:coulomb-social` |
| Workplan | CSOC-WP-0005-T02 |
| Created | 2026-08-12T09:03:47Z |
| Machine record | `service-objectives-and-observations-2026-08.json` |
## Service objectives (application-owned)
These are **planning requirements** for evaluating infrastructure options and
forecast error — not external contractual SLAs.
| Domain | Objective | Notes |
|--------|-----------|--------|
| Availability | 99.5% monthly for app HTTPS | Excludes scheduled maintenance and pure dependency outages |
| Latency | `/healthz` p99 ≤ 500ms | Synthetic-friendly |
| Latency | Auth HTML p95 ≤ 1.5s | App home, session pages |
| Latency | Space markdown p95 ≤ 3s | Includes Forgejo fetch; cold cache separate |
| Recovery | RPO ≤ 60min, RTO ≤ 4h | DB via apps-pg; content via Forgejo policy |
| Retention | DB backups ≥ 30days | Executed by platform; app requires recoverability |
| Retention | App logs ≥ 14days | Platform log sink when wired |
| Isolation | `tenant_id` filter + instance-per-client | No cross-tenant list/detail |
| Auth | NetKingdom OIDC; no local passwords | ADR-0001 |
| Content safety | Sanitized markdown HTML | ADR-0002 |
### Dependency inheritance (not double-owned)
| Dependency | What we inherit |
|------------|-----------------|
| apps-pg | Physical DB backup/restore mechanics |
| reef-railiance k3s | Node/pod availability |
| Forgejo | Markdown durability and git history |
| KeyCape / Authelia | Sign-in path availability |
## Observations (timestamped, non-secret)
| Measure | Value | When | Authority |
|---------|-------|------|-----------|
| public_healthz | ok | 2026-08-12T09:03:47Z | curl app.coulomb.social |
| public_landing_http_status | 200 | 2026-08-12T09:03:47Z | curl |
| demo_fixture_raw_bytes | 586 | 2026-08-12T09:03:47Z | Forgejo public raw |
| declared_replicas | 1 | 2026-08-12 | railiance-apps chart |
| declared_cpu_request_millicores | 100 | 2026-08-12 | chart |
| declared_memory_request_mi | 256 | 2026-08-12 | chart |
| declared_cpu_limit_millicores | 1000 | 2026-08-12 | chart |
| declared_memory_limit_mi | 1024 | 2026-08-12 | chart |
| media_persistence_enabled | false | 2026-08-12 | chart |
| helm_image_tag | 7fcd0cf | 2026-08-12 | helm values |
| portfolio_ready_replicas | 1 | 2026-08-11 | RESOURCE-WP-0003 discovery |
| commissioned_on | 2026-08-09 | inventory | resource-control |
| app_database_gb_logical | **unknown** | 2026-08-12 | needs apps-pg measure |
| production_member_count | **unknown** | 2026-08-12 | needs prod DB shell |
| observed_cpu/memory | **unknown** | 2026-08-12 | cluster metrics authority |
Explicit **unknown** is preferred over invented utilization. Cluster samples
belong in railiance-cluster evidence; logical DB size may be published here
once an operator can run a non-secret `pg_database_size` (or app management
command) without credentials in git.
## Forecast error measurement
After each month, compare demand proxies (T01) to:
1. request/login counts from ingress or app metrics (when exported),
2. logical DB size,
3. member/space counts from app DB,
4. operator hours from T03.
Record actuals as a dated observation append (new file or JSON revision), never
rewrite prior observations.

View file

@ -0,0 +1,81 @@
# Workload operations labor — coulomb-social (2026-08)
| Field | Value |
|-------|--------|
| Resource | `resource:tenant:coulomb:coulomb-social` |
| Workplan | CSOC-WP-0005-T03 |
| Created | 2026-08-12T09:03:47Z |
| Labor rate (planning) | €60 / hour (resource-control convention; not payroll) |
## Boundary — count only app workload labor here
| In scope (coulomb-social) | Out of scope (do not double-count) |
|---------------------------|-------------------------------------|
| App image build/publish for this service | k3s/node host ops (`railiance-infra` / cluster) |
| Helm values / release for coulomb-social | Shared apps-pg admin & backups (`railiance-platform`) |
| Django migrations for this app | Ingress controller / cert-manager fleet work |
| Seed/bind spaces, Forgejo webhook config for app | Forgejo platform upgrades (`railiance-forge`) |
| App incident triage (500s, bad deploy, data bugs) | KeyCape / Authelia / LLDAP platform incidents |
| App restore drill participation (app checklist) | Cluster restore of node/volumes |
| Content/model product ops (spaces ADR, smoke) | Bubble.io hosting (external product until cutover) |
## Setup labor (one-time / infrequent)
Estimates for standing up or re-standing the **Coulomb** production instance.
Recorded for total-cost models; not a timesheet.
| Activity | Hours | Cadence | Notes |
|----------|------:|---------|--------|
| Scaffold app + identity shell (historical CSOC-WP-0002) | 40 | one-time done | sunk; for amortization models only |
| App shell + spaces + Forgejo path (CSOC-WP-0004) | 24 | one-time done | sunk |
| KeyCape client registration / redirect updates | 1 | per host change | script: `scripts/register-keycape-client.sh` |
| Env secret create/rotate (names via railiance-apps tooling) | 0.5 | per rotate | operator; no secret values in repo |
| First deploy + ingress + DNS for app.coulomb.social | 2 | one-time done | with railiance-apps |
| Demo space seed + smoke | 0.5 | per env | `seed_demo_space` + browser checklist |
| Public registration enablement when NK ready | 2 | once | residual CSOC-IN-0001 |
| Bubble migration rehearsal (future CSOC-WP-0001) | 16 | per rehearsal | not yet executed |
**Setup already sunk (approx):** ~66.5 h for current parallel-host MVP
**Near-term setup residual:** ~2.5 h (seed/smoke + registration enable)
**Migration rehearsal (planned):** ~16 h when inventory/mapping ready
## Recurring labor (monthly, steady state)
Aligned with demand scenarios in `demand-forecast-2026-08.json`.
| Activity | low h | base h | high h | Notes |
|----------|------:|-------:|-------:|--------|
| Release: build image, bump tag, deploy, smoke | 1.0 | 1.5 | 2.5 | commit-SHA images |
| Migration apply + verify | 0.25 | 0.5 | 1.0 | only when schema changes |
| Space content ops (seed/bind/webhook, content incidents) | 0.25 | 0.75 | 2.0 | Forgejo is SoR; app refresh path |
| Identity smoke / session diagnostics | 0.25 | 0.5 | 1.0 | Case A; Case B when live |
| Incident response (app-owned) | 0.25 | 0.5 | 1.5 | excludes pure platform outages |
| Recovery exercise (app checklist against restored DB) | 0 | 0.25 | 0.5 | quarterly average as monthly |
| Demand/evidence refresh | 0 | 0 | 0.5 | forecast vs actual notes |
| **Total recurring** | **2.0** | **4.0** | **8.0** | matches T01 operator_hours |
### Monthly labor € proxy (internal only)
| Scenario | Hours | € @ 60 |
|----------|------:|-------:|
| low | 2 | 120 |
| base | 4 | 240 |
| high | 8 | 480 |
## What not to bill twice
- **Cluster upgrade weekend** → railiance-cluster / infra labor, even if app is
redeployed as a consumer.
- **apps-pg vacuum/backup failure** → platform labor; app only spends the hours
verifying app health after restore.
- **Forgejo disk full** → forge labor; app may open an incident for missing
content but content storage is not app labor ownership.
- **Authelia MFA outage** → NetKingdom; app documents customer impact only.
## Evidence links
- Operator runbook: `docs/deploy.md`
- Identity smoke: `docs/identity/smoke.md`
- Spaces content: `docs/spaces-content.md`
- Residuals: `docs/intakes/csoc-residuals.md`
- Demand: `docs/resource-evidence/demand-forecast-2026-08.md`

View file

@ -4,11 +4,11 @@ type: workplan
title: "Publish Coulomb Social resource demand and cost evidence"
domain: communication
repo: coulomb-social
status: ready
status: finished
owner: codex
topic_slug: coulomb-social
created: "2026-08-11"
updated: "2026-08-11"
updated: "2026-08-12"
related:
- CSOC-WP-0004
- RESOURCE-WP-0003
@ -24,11 +24,13 @@ Publish workload-owned requirements and observations for
or financial authority into this repository. Origin: `RESOURCE-WP-0003-T04`
delegated evidence gap.
**Finished 2026-08-12:** evidence under `docs/resource-evidence/`.
## T01 — Declare low, base, and high workload demand
```task
id: CSOC-WP-0005-T01
status: todo
status: done
priority: high
state_hub_task_id: "d22e42e7-186e-4206-9cf4-c533d6388b13"
```
@ -40,11 +42,15 @@ multi-tenant scenarios.
Done when resource-control can produce falsifiable monthly demand forecasts.
2026-08-12: `docs/resource-evidence/demand-forecast-2026-08.{md,json}`
low/base/high with units, assumptions, instance-per-client scope (external
clients = separate instances), labor € proxy, capacity floor from chart.
## T02 — Publish service objectives and usage observations
```task
id: CSOC-WP-0005-T02
status: todo
status: done
priority: high
state_hub_task_id: "ac078211-3cd9-49d8-baac-c0a1b60278fb"
```
@ -57,11 +63,15 @@ owners.
Done when infrastructure options can be evaluated against application-owned
requirements and forecast error can be measured.
2026-08-12: `docs/resource-evidence/service-objectives-and-observations-2026-08.{md,json}`
— SLOs + timestamped measures; explicit **unknown** for prod DB size, member
count, and live CPU/memory (cluster/DB authority).
## T03 — Record workload operations labor
```task
id: CSOC-WP-0005-T03
status: todo
status: done
priority: medium
state_hub_task_id: "a15e630c-6bec-4c50-9622-bce16e1ce418"
```
@ -73,8 +83,16 @@ cluster, database, identity, and provider labor.
Done when resource-control can include workload labor without double counting
platform operations.
2026-08-12: `docs/resource-evidence/workload-operations-labor-2026-08.md`
setup sunk vs residual, recurring low/base/high hours matching demand, out-of
scope table for platform labor.
## Acceptance
- [ ] Demand forecasts distinguish current, growth, and external-tenant cases.
- [ ] Service objectives and observations have stable units and provenance.
- [ ] Workload labor is separated from delegated infrastructure labor.
- [x] Demand forecasts distinguish current, growth, and external-tenant cases.
- [x] Service objectives and observations have stable units and provenance.
- [x] Workload labor is separated from delegated infrastructure labor.
## Index
`docs/resource-evidence/README.md`