Console Pages
What each DevEx page in the console shows, the endpoint it reads, and the rules that endpoint keeps. Every /api/* GET here needs a console session. The handlers live in grpc/operators/core/internal/console/.
200 even when they could not read a source, and say so in the body (source:"unavailable", complete:false, or an unmeasured[] list with the API’s own reason). An empty list means “none”; an unreadable one is reported as unmeasured, never as empty.Cluster
Namespaces — GET /api/namespaces
For every tracked namespace plus every tenant client-* namespace, it shows the admission posture (PSA labels, NetworkPolicies), the workloads (Deployments, DaemonSets, StatefulSets) with ready/desired, their pods (restarts, node, why the last container died), Warning events, and ResourceQuota/LimitRange. Each namespace closes with one verdict and the sentence that justifies it (cluster_namespaces.go). A “Nodes & placement” sub-tab covers nodes; GET /api/nodes/{name} inspects one node.
Container logs are not read, by design: they can carry secrets, and the operator’s ServiceAccount has no pods/log grant.
Instances — GET /api/dashboard
The tenant instances (ClientInstances) with app, tenant, role, phase, readiness and endpoint, plus the FinOps attribution projected from the CR (initiative, compute units, tier, expiry).
Routing — GET /api/routes
How traffic reaches each service, and whether the thing it reaches is serving (routing.go). The routing table is the DEVGW_ROUTES env var on the devgateway DaemonSet, plus the console’s own /forge/ path. The table’s origin is reported as:
| Origin | Meaning |
|---|---|
pod | read from a running devgateway pod, so it is what routes right now |
daemonset | read from the DaemonSet template, so it is what the next pod gets |
declared | the committed manifest, used only when the edge could not be read. No route with this origin is called “serving” |
Platform audit
An assessment page, not the audit log. It gives one verdict over checks read from existing endpoints: /api/audit/verify, /api/gitops, /api/doctor, /api/metrics, /api/reviews, /api/compliance and /api/governance-runs. A check that could not be read goes into a not-measured list, never counted as a pass. Its only actions are the ones the console already has: re-verify the audit chain, and start a review through an existing playbook’s PlaybookService.RunNow.
GitOps — GET /api/gitops
The response has one of two modes:
mode: "argo": the Argo CD Application list could be read. It shows sync and health per Application.mode: "github-actions": Argo CD is absent, which is the case on the real box, where delivery iscore-cd.yml. Per workload (gitops_delivery.go):- desired is the newest
core-cd.ymllineage record that planned or rolled the workload: commit, image, digest, run, time and roll verdict; - running is the pods’
imageIDdigests and the placement labels on the live object (app.kubernetes.io/version,runink.org/commit); - drift is decided on the digest when the build reported one, else on the commit label. It is
nullwhen neither pair was read, neverfalse.
- desired is the newest
The commit stamp. core-cd.yml’s roll job makes one strategic-merge kubectl patch of the pod template. It sets the label runink.org/commit=<sha> and the annotation kubectl.kubernetes.io/restartedAt, and re-pins every container on the node-local registry to :latest@<digest>. If the patch cannot be built or is refused, it falls back to a plain rollout restart with a ::warning.
kubectl rollout restart restarts onto the same digest. To move a workload onto new bits, dispatch core-cd.yml with images=<img>.“Did my commit ship?” is Deploy lineage (GET /api/lineage?sha=), on the Intelligence rail. See Data audit & deploy lineage.
Audit chain — GET /api/audit, GET /api/audit/{seq}, GET /api/audit/verify
The console’s hash-chained audit log, persisted in its appfs root at var/log/audit and continued across restarts (auditlog.go, auditread.go).
GET /api/auditreturns a newest-first page and the chain’s verdict. Filters:actor,action,outcome(success,deniedorfailure),since(RFC 3339 or a duration such as24h),limit,before(a chain seq).GET /api/audit/{seq}returns one record in full with its neighbours’ hashes. Records carryseq,at,actor,action,target,outcome,detail,hash,prevHash.GET /api/audit/verifywalks the persisted chain and names the first record that was edited, removed, inserted or reordered.200withok:falseis a detected break;503means there is no persisted chain to check.
Only session admins may read the trail, and the check fails closed. It is cross-identity data: sign-ins, refused sign-ins, revocations, and who armed which agent.
Session admins — GET/PUT /api/session-admins
Who may read the audit trail and end another person’s sessions. The list is a document in this installation’s metastore (var/lib/session-admins), not a manifest value (sessionadmins.go):
CONSOLE_SESSION_ADMINSincore-console-secretsis the first-boot seed only. Once the document is written, changing the Secret changes nothing.- Unset means nobody, unlike the other admin lists.
PUTreplaces the list. It refuses an empty list, and one that drops the caller unless someone else remains. Every attempt is audited, and a change is made durable in the audit log before it is written.- A console with no sign-in configured refuses:
this console has no sign-in configured, so no identity can be a session admin.
Doctor — GET /api/doctor
Traces agents/opsdoctor, which is a k8s CronJob rather than a workflow so that it keeps working when the runner pool it watches is dead. It shows the CronJob’s state (present, SUSPENDED, schedule, last run times), its recent Jobs and how they ended, and the tracking issue (label ops-doctor) with the time of the last report. Suspension is surfaced first, because a benched doctor looks exactly like a healthy quiet one.
Metrics — GET /api/metrics
Measured process and workload metrics only (procmetrics.go). They come from this process (runtime and /proc) and the live API server. There is deliberately no uptime percentage and no network throughput, because the process cannot measure either. Every block carries a source.
Live CPU and memory come from metrics.k8s.io when it exists. When it is absent (as on the real box), they come from the kubelet Summary API through the apiserver node proxy (kubeletstats.go), labelled source: "kubelet-summary". That needs nodes/proxy get on the core-operator ClusterRole. A 403 is reported as unmeasured, never zero.
Agent runs
Runs — GET /api/runs
One history of every autonomous agent run, whatever started it (runs.go). It is a view over stores that already exist:
| Kind | Source |
|---|---|
| workflow agents | the run store, all repos it holds, topped up through /api/agent-runs’ staleness gate |
| pipelines | the run store’s pipeline runs |
| CLI sessions | the session-report table and your own command queue |
| CronJobs | the batch Jobs of opsdoctor, certsync, ddnssync and imagesync (no log: pods/log is not granted) |
| app agents | /api/agent-health, latest only |
The “Schedules & arming” tab reads GET /api/agent-schedules. See Agent Fleet.
Reviews — GET /api/reviews, GET /api/reviews/{type}/{runId}[/detail]
What each review found, per type (reviews.go):
| Type | Runs from | Findings from |
|---|---|---|
code | run store: reviewer, fixer, resolver | open PRs labelled review:findings-open |
risk | run store: risk | the “🛡️ Dependency risk report” issue |
compliance | run store: compliance | /api/compliance plus the “📋 Compliance status” issue headline |
dataQuality | run store: datagov, plus datagov assessments and CapEx scan runs | the datagov findings store and the CapEx findings |
dataConsistency | CapEx scan runs | the CapEx “Consistency” control dimension |
aiMaturity, dataMaturity | none, computed on read | /api/maturity |
A run whose findings were not recorded carries summary: null and findingsStored: false with the reason, never a zero count that would read as a clean review. /detail adds what the agents posted on GitHub.