Compare commits

...

794 Commits

Author SHA1 Message Date
jenkins
7f5508c930 feat(ariadne): declare the fix categories, and show them beside the actions
The three categories now appear in the deployment next to the action
allowlist, and the monitor prints both at the policy gate so the difference is
visible during a demo rather than asserted: two ids Ariadne may execute on its
own authority, three categories it may only ask Hermes to propose a patch for.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 20:46:55 -03:00
jenkins
4fe4162dad fix(demo): make the monitor's evidence correct for the code demo too
Some checks failed
Tests / Declarative: Post Actions testing.tests.test_repo_structure.test_knowledge_service_mirror_matches_source failed
Several stages assumed the fixture job. On a code-demo run they would have
read the fixture ConfigMap, which is unrelated to that job, and asserted that
a field changing from unhealthy to healthy was the repair - flatly false when
the outcome is a pull request. The route stage would also have claimed the
source-proposal branch was not taken during the run that takes it.

The stages now branch on the job: the route explains that a source fix is not
a registered action and so takes the proposal branch; the response points at
the pull request recorded on the incident; and the verify stage says the
branch build validates the proposal and that nothing merges without a human
whatever it reports.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 20:07:10 -03:00
jenkins
4ffcae8b3c fix(gitea): raise the memory ceiling; time-bound the demo cleanup calls
Gitea wedged at 2073Mi against a 2Gi limit: its API stopped answering even on
its own loopback, and the repeated SSH LoginGraceTime drops in its log were
starvation symptoms rather than a separate fault. Raised to 3Gi.

The reset command's Gitea calls had no --max-time, so a slow service became an
indefinite hang with no output - the script appeared frozen after 'clearing
bstein/hermes-code-demo'. They now fail after 25 seconds and say so.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 20:04:42 -03:00
jenkins
4e3be5f7e4 fix(demo): frame the control decision and the branch endings clearly
Some checks failed
Tests / Declarative: Post Actions testing.tests.test_repo_structure.test_knowledge_service_mirror_matches_source failed
Five corrections to how the monitor explains itself, plus code-demo support.

The authorization is now described as what it is: a match between an action
Ariadne already has code to perform, an action id Hermes is permitted to
request, and the action Hermes actually recommended. Hermes cannot add to that
registry, and the recommendation proceeds only because it names something
already in it. The editorial line about pull requests is gone.

The repair now reads as a change rather than a value: the field said unhealthy
when the build failed and says healthy because Ariadne just patched it.

The rebuild stage says plainly that the operational branch is complete and
that neither the human-required response nor the optional source proposal is
entered, rather than leaving the reader to infer it from silence.

Inspectable outputs states that on the chart it is the 'records and artifacts'
edge out of the whole response box, not out of one branch - every path ends
there. The layout makes it look like it belongs to the proposal path alone.

 follows hermes-code-demo, which takes the proposal branch: the
stages now read as a patch proposal and a pull request instead of being
mislabelled an escalation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 19:18:31 -03:00
jenkins
7d40355ff6 feat(demo): show what each source contributed to the bundle
Some checks failed
Tests / Declarative: Post Actions testing.tests.test_repo_structure.test_knowledge_service_mirror_matches_source failed
The bundle stage printed the job allowlist, which says nothing about what was
actually sent. It now samples the bundle itself, rebuilt with the same
collector Ariadne used - durable, because a finished build's console does not
change - and trimmed hard, since the point is to show what each source
contributes rather than to reprint it:

    jenkins.console_failures : 1 region(s), truncated=False
       | ERROR: Demo fixture check failed for incident hermes-triage-demo/34
    jenkins.failed_tests     : 0
    log_evidence.records     : 35 from OpenSearch kube-*
       | [jenkins] Using /home/jenkins/agent/remoting as a remoting work directory

The diagnosis now also carries run_id and run_seconds, so the run can be
opened in the Hermes dashboard to see the prompt it was given and every tool
call it made - the request, not just the answer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 18:57:44 -03:00
jenkins
bbb4f6b185 fix(demo): show durable evidence at detection, not live cluster state
Some checks failed
Tests / Declarative: Post Actions testing.tests.test_repo_structure.test_knowledge_service_mirror_matches_source failed
The detection stage read the fixture ConfigMap, which is a live value. Every
stage describes a moment that has passed, and by the time detection is
narrated the repair has already run - so it printed 'healthy' and implied the
fixture had never failed, contradicting the premise of the whole run. Replay
made it certain rather than occasional.

It now prints the incident's recorded state changes, which stay true
afterwards:

    21:48:03 detected         {result: FAILURE, ...}
    21:48:18 diagnosed        {classification: known_demo_fixture_failure, ...}
    21:48:18 repairing        {action: repair_demo_fixture}
    21:48:18 awaiting_rebuild {repair: configmap_patch, ...}
    21:49:03 resolved         {resolved_by_build: 35}

The fixture read stays on the response stage, where healthy is the point.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 18:52:08 -03:00
jenkins
6da0cba96d feat(demo): show the diagnosis and the policy result, in the chart's own words
Some checks failed
Tests / Declarative: Post Actions testing.tests.test_repo_structure.test_knowledge_service_mirror_matches_source failed
The Hermes stage proved only that a pod was running, when the substantive
evidence is the JSON Hermes returned; it now reads that from the audit trail,
so classification, confidence and requested action are on screen. The policy
gates stage never showed the verdict it was describing; authorized and
authorize_reason now appear with it.

The chart paths were paraphrases. They are now the node labels from
mermaid/TestAutomation.mmd verbatim - Response check, Scoped repair guard,
Action authorizer, Action registry, Scoped ConfigMap repair, Action result -
so a line on screen can be found on the diagram. The checklist carries them too.

The Optional source proposal branch was silently absent rather than explained.
The route stage now states it was not taken and why: a fixture repair is an
operational action, and source fixes are not actions at all.

Also widened the log window and its timeout; the fetch occasionally exceeded
25s and dropped a poll.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 18:14:13 -03:00
jenkins
39cc744d19 fix(demo): stop the monitor losing steps to poll timing
Some checks failed
Tests / Declarative: Post Actions testing.tests.test_repo_structure.test_knowledge_service_mirror_matches_source failed
It read only the newest tick from a --tail=400 window. Ariadne now emits
roughly two hundred lines per tick, so that window held about two ticks, and a
step that lasted a single tick - the repair, most importantly - vanished if a
poll landed after it. Build 30 was triaged, repaired and resolved correctly
while the monitor printed nothing at all.

The window is now time-based, and every tick inside it is replayed rather than
just the last. mark() was already idempotent, so replaying is safe and the
monitor recovers stages it was not running for.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 17:32:51 -03:00
jenkins
51338921b6 fix(demo): read the container exit code, not the Job status
Some checks failed
Tests / Declarative: Post Actions testing.tests.test_repo_structure.test_knowledge_service_mirror_matches_source failed
The fixture check waited on the Job's .status.succeeded/.status.failed. The
kubelet records a container's exit code the instant it stops, but the Job
controller reconciles those fields on its own schedule: observed at 2m24s,
4m27s and 34m on this cluster for identical work. During that window the pod
had already exited 1 and the build sat printing 'Will try again after 13 sec',
looking hung long after the test had finished, which is unusable in a demo.

Polling the pod's terminated exitCode removes the controller from the path
entirely. The Job is still used, so the evidence shape is unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 17:11:45 -03:00
flux-bot
a2e167895b chore(cassandra): automated image update 2026-08-06 20:02:10 +00:00
flux-bot
5d5110672c chore(cassandra): automated image update 2026-08-06 19:58:14 +00:00
flux-bot
fc571aa434 chore(cassandra): automated image update
Some checks failed
Tests / Declarative: Post Actions testing.tests.test_repo_structure.test_knowledge_service_mirror_matches_source failed
2026-08-06 19:54:19 +00:00
flux-bot
3868286f7c chore(cassandra): automated image update 2026-08-06 19:54:11 +00:00
flux-bot
36b61470b5 chore(cassandra): automated image update
Some checks failed
Tests / Declarative: Post Actions testing.tests.test_repo_structure.test_knowledge_service_mirror_matches_source failed
2026-08-06 19:45:07 +00:00
jenkins
16d771aa69 feat(demo): narrate the monitor by echoing the commands it runs
An audience watching a dashboard has to take the result on trust. Watching
kubectl run against the cluster and reading the raw answer is the difference
between a demonstration and an assertion, so every read is now printed before
it runs and its output shown beneath.

Each stage also names the service UI worth looking at when something changes
there: the Jenkins job as the build goes red and again when the rebuild starts
on its own, the Hermes dashboard as the agent run appears, the Grafana triage
panels and the filed issues at the end.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 16:41:35 -03:00
flux-bot
cf9cc19d13 chore(cassandra): automated image update 2026-08-06 19:41:13 +00:00
flux-bot
6595600afc chore(cassandra): automated image update
Some checks failed
Tests / Declarative: Post Actions testing.tests.test_repo_structure.test_knowledge_service_mirror_matches_source failed
2026-08-06 19:37:15 +00:00
flux-bot
29b1eae746 chore(cassandra): automated image update 2026-08-06 19:37:03 +00:00
jenkins
db14a9d6a8 feat(demo): name the chart branch the monitor is taking
Some checks failed
Tests / Declarative: Post Actions testing.tests.test_repo_structure.test_knowledge_service_mirror_matches_source failed
An audience following mermaid/TestAutomation.mmd needs to know which edge off
the route diamond was taken, which was the one thing the output left them to
infer. It now quotes the chart's own labels - authorized action, human
required, and the optional code path - and the docstring states where the
reporting is coarser than the diagram.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 16:25:39 -03:00
jenkins
e93dc7b8a8 feat(demo): reset deletes the demo repositories' issues, PRs and branches
A rerun should start from nothing. DEMO_REPOS is the entire blast radius and
is deliberately explicit: a real service's issues are genuine triage records,
and clearing them to tidy a demo would destroy the evidence the system exists
to produce.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 16:24:53 -03:00
jenkins
e9c2d84dbc fix(demo): print the monitor summary once per incident
Some checks failed
Tests / Declarative: Post Actions testing.tests.test_repo_structure.test_knowledge_service_mirror_matches_source failed
The resolving tick remains the newest one until another incident opens, so the
completed checklist reprinted on every poll.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 16:07:03 -03:00
jenkins
93ff096fa1 feat(demo): add reset and monitor commands
Some checks failed
Tests / Declarative: Post Actions testing.tests.test_repo_structure.test_knowledge_service_mirror_matches_source failed
reset restores only what the demo itself creates: the fixture ConfigMap, the
demo repository's open repair pull requests and their branches. Real service
repositories are deliberately untouched - those issues are genuine triage
records, and deleting them to tidy a demo would destroy the evidence the
system exists to produce.

monitor streams the flow in the stages of mermaid/TestAutomation.mmd, so a
second terminal can be followed against the chart: Detect and gather, Hermes
analysis, Ariadne policy gates, Ariadne response, Inspectable outputs. Each
stage prints the evidence that moved it, since the claim the chart makes is
that every step is answerable from data rather than asserted. Read-only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 15:57:02 -03:00
flux-bot
f96fb30522 chore(maintenance): automated image update 2026-08-06 18:53:56 +00:00
jenkins
cd974baeeb fix(demo): print the triage allowlist as a list, not raw config
Some checks failed
Tests / Declarative: Post Actions testing.tests.test_repo_structure.test_knowledge_service_mirror_matches_source failed
The allowlist is the outermost safety boundary - a job absent from it is never
touched, whatever fails - so it is worth reading at a glance rather than as a
comma-separated setting value echoed verbatim.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 15:48:57 -03:00
jenkins
5364cc9666 fix(demo): source local credentials and stop waiting on a job that no longer exists
The script demanded JENKINS_USER and JENKINS_TOKEN in the environment and said
only 'set JENKINS_USER' when they were missing, which is not enough to act on.
It now sources scripts/ops/hermes_triage_demo.env, git-ignored so it can hold
real tokens, and names that file when credentials are absent. An example file
records what belongs in it.

The fixture command also still polled for a hermes-demo-repair-<build> Job.
That Job stopped existing when the repair became an in-process ConfigMap
patch, so the command would have waited its full 400 seconds and then reported
nothing. It now watches the fixture returning to healthy, which is what
actually happens.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 15:45:15 -03:00
flux-bot
7e2b983956 chore(maintenance): automated image update
Some checks failed
Tests / Declarative: Post Actions testing.tests.test_repo_structure.test_knowledge_service_mirror_matches_source failed
2026-08-06 18:36:56 +00:00
flux-bot
d8935d0359 chore(cassandra): automated image update
Some checks failed
Tests / Declarative: Post Actions testing.tests.test_repo_structure.test_knowledge_service_mirror_matches_source failed
2026-08-06 17:57:35 +00:00
flux-bot
f57809c74e chore(cassandra): automated image update 2026-08-06 17:53:48 +00:00
flux-bot
a17e67e8fd chore(cassandra): automated image update 2026-08-06 17:49:47 +00:00
flux-bot
52a4768413 chore(cassandra): automated image update 2026-08-06 17:48:33 +00:00
jenkins
fa7c213927 docs(hermes): drop demonstration framing from the agent workspace
Some checks failed
Tests / Declarative: Post Actions testing.tests.test_repo_structure.test_knowledge_service_mirror_matches_source failed
START-HERE.md is visible in the Hermes dashboard, so its wording is part of
what an operator sees. Describing the runbook as a five-minute demonstration
frames the automation as a set piece rather than as something that runs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 14:26:13 -03:00
flux-bot
e205eaf8fa chore(maintenance): automated image update
Some checks failed
Tests / Declarative: Post Actions testing.tests.test_repo_structure.test_knowledge_service_mirror_matches_source failed
2026-08-06 17:17:37 +00:00
flux-bot
9014579cd8 chore(cassandra): automated image update 2026-08-06 17:15:24 +00:00
flux-bot
af5245699c chore(cassandra): automated image update 2026-08-06 17:12:02 +00:00
flux-bot
52c287598c chore(cassandra): automated image update
Some checks failed
Tests / Declarative: Post Actions testing.tests.test_repo_structure.test_knowledge_service_mirror_matches_source failed
2026-08-06 17:09:09 +00:00
flux-bot
9e066d1980 chore(cassandra): automated image update 2026-08-06 17:08:01 +00:00
flux-bot
7a0d30005e chore(maintenance): automated image update
Some checks failed
Tests / Declarative: Post Actions testing.tests.test_repo_structure.test_knowledge_service_mirror_matches_source failed
2026-08-06 16:18:37 +00:00
jenkins
8b930632ca feat(ariadne): widen code and issue coverage, cap open proposals at 64
ananke, pegasus and atlasbot were triaged but mapped to no repository, so
their failures could produce neither an issue nor a pull request. All three
exist on main and are non-empty. data-prepper is deliberately absent: it has
no repository under bstein.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 13:10:20 -03:00
flux-bot
ddb8561cc0 chore(maintenance): automated image update
Some checks failed
Tests / Declarative: Post Actions testing.tests.test_repo_structure.test_knowledge_service_mirror_matches_source failed
2026-08-06 15:51:58 +00:00
jenkins
ff731b887e docs: second pass of diagram corrections after the remaining work landed
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 12:45:00 -03:00
flux-bot
378169597c chore(maintenance): automated image update 2026-08-06 15:42:00 +00:00
jenkins
32de26e4a5 feat(ariadne): map bstein-dev-home to its own log namespace
Some checks failed
Tests / Declarative: Post Actions testing.tests.test_repo_structure.test_knowledge_service_mirror_matches_source failed
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 12:33:19 -03:00
jenkins
5286723b58 fix(hermes): run the Vault agent before the pod's own init containers
Some checks failed
Tests / Declarative: Post Actions testing.tests.test_repo_structure.test_knowledge_service_mirror_matches_source failed
The agent init container is appended by default, so init-config ran before
/vault/secrets existed and fell back to the Secret every time. The values were
identical, so the migration appeared to work while Vault was never actually
read.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 12:20:53 -03:00
jenkins
83f8d67640 feat(hermes): take the Anthropic credential from Vault
Some checks failed
Tests / Declarative: Post Actions testing.tests.test_repo_structure.test_knowledge_service_mirror_matches_source failed
The Claude subscription OAuth token was created as a manual kubectl Secret in
the interest of demo time, with migration to Vault agreed as follow-up. The
value now lives at kv/atlas/hermes/agent-tokens and is injected as a file.

The hermes role gains that path and binds the hermes-triage service account
the deployment actually runs as; it previously bound only hermes-vault. The
init container prefers the Vault file and falls back to the Secret, so this
can be rolled back by removing the annotations alone, and the Secret should be
deleted once Vault has been serving it for a while.

Vault was reachable all along without the operator credential: Ariadne already
holds a vault-admin Kubernetes auth role, which is how the value was written.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 12:14:52 -03:00
flux-bot
ed626a1f7a chore(cassandra): automated image update 2026-08-06 15:10:48 +00:00
flux-bot
6bdc52c528 chore(cassandra): automated image update 2026-08-06 15:06:55 +00:00
flux-bot
0a6ac477cd chore(cassandra): automated image update
Some checks failed
Tests / Declarative: Post Actions testing.tests.test_repo_structure.test_knowledge_service_mirror_matches_source failed
2026-08-06 15:02:54 +00:00
flux-bot
2644f0c5ee chore(cassandra): automated image update 2026-08-06 15:02:45 +00:00
flux-bot
53ab15cb20 chore(maintenance): automated image update
Some checks failed
Tests / Declarative: Post Actions testing.tests.test_repo_structure.test_knowledge_service_mirror_matches_source failed
2026-08-06 14:11:41 +00:00
flux-bot
0d2ca9ed15 chore(maintenance): automated image update
Some checks failed
Tests / Declarative: Post Actions testing.tests.test_repo_structure.test_knowledge_service_mirror_matches_source failed
2026-08-06 13:47:28 +00:00
flux-bot
4e4f4fe8a4 chore(maintenance): automated image update 2026-08-06 13:46:29 +00:00
flux-bot
5eb9f7811c chore(maintenance): automated image update 2026-08-06 13:44:28 +00:00
flux-bot
4dcdf666dc chore(maintenance): automated image update
Some checks failed
Tests / Declarative: Post Actions testing.tests.test_repo_structure.test_knowledge_service_mirror_matches_source failed
2026-08-06 13:39:24 +00:00
flux-bot
e0563d93e8 chore(maintenance): automated image update
Some checks failed
Tests / Declarative: Post Actions testing.tests.test_repo_structure.test_knowledge_service_mirror_matches_source failed
2026-08-06 10:37:52 +00:00
jenkins
a9fff9e46b docs: junit and pipeline-stage-view are live; record why it was never blocked
Some checks failed
Tests / Declarative: Post Actions testing.tests.test_repo_structure.test_knowledge_service_mirror_matches_source failed
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 06:50:39 -03:00
flux-bot
086fd52eca chore(maintenance): automated image update 2026-08-06 09:48:13 +00:00
jenkins
5b29be1010 docs: record the TestAutomationV2 diagram corrections
Some checks failed
Tests / Declarative: Post Actions testing.tests.test_repo_structure.test_knowledge_service_mirror_matches_source failed
Written up rather than applied, since the diagram is maintained in parallel.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 06:18:16 -03:00
jenkins
8b0afb672b fix(ariadne): let the pod finish booting before liveness judges it
The earlier probe fix addressed slow /health responses under load, but the
restarts continued with a different signature: connection refused rather than
timeout, meaning the app was not listening yet. Ariadne runs migrations and
builds its cron schedule before binding, which can outlast what liveness
allows from initialDelaySeconds, so the kubelet kept restarting a pod that
was merely still starting.

Add a startupProbe granting up to five minutes to come up, after which
liveness takes over unchanged. This is the case startupProbe exists for.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 06:16:09 -03:00
flux-bot
59565b4cda chore(maintenance): automated image update 2026-08-06 09:11:55 +00:00
jenkins
162c59047d feat(jenkins): install junit and pipeline-stage-view
Without the junit plugin the pipeline's junit step throws NoSuchMethodError,
jenkins.failed_tests is always empty, and auto-triage has only raw console
text to reason from. That was the root of two separate diagnosis failures:
the enforced failure being crowded out of the evidence budget, and the
patcher being unable to locate the defective source file.

Pinned to junit 1369.v15da_00283f06, the newest release that runs on core
2.528.3 - the current 1418 requires 2.533. scm-api moves 724 -> 728 because
the workflow-cps these pull in requires it; verified the whole 30-plugin
dependency closure needs no core newer than 2.528.3, and rehearsed the exact
jenkins-plugin-cli install in a throwaway pod before committing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 06:08:17 -03:00
flux-bot
e1a1922b70 chore(maintenance): automated image update
Some checks failed
Tests / Declarative: Post Actions testing.tests.test_repo_structure.test_knowledge_service_mirror_matches_source failed
2026-08-06 08:55:38 +00:00
flux-bot
3a503b7a82 chore(maintenance): automated image update 2026-08-06 08:22:34 +00:00
flux-bot
922c90274c chore(cassandra): automated image update 2026-08-06 08:21:47 +00:00
jenkins
3461a4e99d fix(ariadne): stop the kubelet killing a healthy triage pod
The liveness probe used the default timeoutSeconds of 1. The auto-triage tick
runs every minute and spends most of it waiting on Jenkins, OpenSearch, Gitea
and Hermes, so against a 500m CPU limit /health occasionally answers in over a
second. Three of those and the container is killed, dropping triage ticks for
the length of a restart. Observed 11 times in 139 minutes, with the pod
sitting 1/2 Ready and restarting repeatedly.

Give both probes a 5s timeout and let liveness tolerate five failures, so a
busy tick is no longer mistaken for a hung process.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 05:20:35 -03:00
flux-bot
fbe61f6cb9 chore(cassandra): automated image update 2026-08-06 08:17:41 +00:00
jenkins
dbef8d6244 docs(runbook): Claude primary, hung-build escalation, narrowed alerting
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 05:15:42 -03:00
flux-bot
913f5953ab chore(cassandra): automated image update 2026-08-06 08:15:03 +00:00
flux-bot
cc8e08d6dc chore(cassandra): automated image update 2026-08-06 08:14:29 +00:00
jenkins
fb1131053a feat(hermes): switch the primary model to Claude Opus 5
The Codex weekly limit is close, so make anthropic/claude-opus-5 primary and
demote openai-codex to first fallback with the local gpt-oss:20b behind it.

The credential is a Claude subscription OAuth token, not an API key. The
anthropic provider resolves ANTHROPIC_API_KEY, then ANTHROPIC_TOKEN, then
CLAUDE_CODE_OAUTH_TOKEN, so the OAuth token must arrive under the last name
to be treated correctly. Marked optional so Hermes still starts and falls
back if the Secret is absent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 05:00:09 -03:00
jenkins
9c0af401e9 docs: add cluster architecture diagrams 2026-08-06 04:42:12 -03:00
jenkins
15c27d85f6 docs: record the first autonomous source repair on a real service repo
ariadne PR #3 from incident ariadne/409, chosen path ariadne/utils/errors.py -
a file that appears nowhere in the build console and was reached only by
following the failing test's imports.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 04:28:54 -03:00
flux-bot
003b2272ce chore(cassandra): automated image update 2026-08-06 06:50:03 +00:00
flux-bot
44a11d0765 chore(cassandra): automated image update 2026-08-06 06:46:05 +00:00
flux-bot
0cdfba4c02 chore(cassandra): automated image update 2026-08-06 06:42:06 +00:00
flux-bot
a7b205d1ec chore(cassandra): automated image update 2026-08-06 06:40:06 +00:00
flux-bot
aff0f9b18a chore(maintenance): automated image update 2026-08-06 04:47:42 +00:00
jenkins
d0753c5eb1 docs: state what each demo proves and record the two selection defects
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 01:39:55 -03:00
flux-bot
f44ed2f953 chore(maintenance): automated image update 2026-08-06 04:33:02 +00:00
flux-bot
e7b0a62197 chore(cassandra): automated image update 2026-08-06 04:27:41 +00:00
flux-bot
0b81f744db chore(cassandra): automated image update 2026-08-06 04:24:11 +00:00
flux-bot
10341a6ede chore(cassandra): automated image update 2026-08-06 04:19:49 +00:00
flux-bot
1bdc713d9a chore(cassandra): automated image update 2026-08-06 04:19:31 +00:00
flux-bot
e305350dc6 chore(maintenance): automated image update 2026-08-06 04:10:10 +00:00
jenkins
e3e40f4618 feat(monitoring): make Hermes triage email mean something
Every human_required escalation already files an issue in the failing
service's own repository, and mailing on each one made the inbox the loudest
and least informative output of the system. Replace the blanket alert with
two narrow ones: a repair that ran and failed, which is the only case where
the automation acted and left things no better, and an escalation still
untouched after six hours.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 00:26:34 -03:00
flux-bot
67839af124 chore(maintenance): automated image update 2026-08-06 03:11:10 +00:00
flux-bot
9aca7cd52b chore(maintenance): automated image update 2026-08-06 03:09:11 +00:00
flux-bot
57691698ed chore(maintenance): automated image update 2026-08-06 03:07:10 +00:00
flux-bot
160636197c chore(maintenance): automated image update 2026-08-06 03:02:09 +00:00
flux-bot
13934a6be7 chore(bstein-dev-home): automated image update 2026-08-06 02:56:09 +00:00
flux-bot
fff0ed9062 chore(bstein-dev-home): automated image update 2026-08-06 02:52:08 +00:00
jenkins
1e67bab40a docs: record the in-process repair timings and the classification-bleed fix
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 23:36:58 -03:00
jenkins
b9b1634833 docs(runbook): record the faster in-process repair timings
Red-build to green-rebuild is 1m04s on Ariadne 0.1.0-402, down from 4m00s
armed-to-resolved when the repair spawned its own Kubernetes Job. Reframe the
timings around the red build, since the arming leg depends on the agent pool.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 23:34:09 -03:00
flux-bot
f400c0116e chore(maintenance): automated image update 2026-08-06 02:25:00 +00:00
jenkins
c7f43339c4 docs(runbook): warn that a full agent pool stalls the demo
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 23:20:30 -03:00
jenkins
b233630007 feat(demo): preflight the agent-pool cap and open code-demo PRs
Two conditions silently break a rehearsal. A saturated Kubernetes agent pool
leaves the demo build queued reporting that all nodes are offline, and an open
hermes-repair PR makes the duplicate guard refuse a new proposal. Report both
so the operator sees them before starting rather than mid-demo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 23:19:37 -03:00
jenkins
15eec62162 feat(ariadne): allowlist the transient-infra retry action
The classification->action registry already mapped transient_infra_failure to
retry_transient_infra, but the action allowlist held only repair_demo_fixture,
so that route always died at the action_not_allowlisted gate. Add the action
and state the registry explicitly rather than relying on the code default.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 23:17:32 -03:00
flux-bot
77b6bfcdf4 chore(maintenance): automated image update 2026-08-06 02:10:50 +00:00
jenkins
d19feb1659 fix(vault-injector): run two replicas so restarts cannot skip injection
The webhook is failurePolicy: Ignore, so with one replica any pod created
during an injector restart is admitted without its Vault agent sidecar and
then crash-loops forever on a missing /vault/secrets file, with nothing to
indicate injection was skipped. Hit twice while rolling ariadne.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 22:56:49 -03:00
flux-bot
7cb266b5f1 chore(maintenance): automated image update 2026-08-06 01:49:38 +00:00
jenkins
2f9c9afcf4 chore(hermes-triage-demo): retire repair Job service account and role
The fixture repair now runs in-process inside Ariadne, so the
hermes-demo-repair service account and its Role/RoleBinding have no
remaining user.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 22:31:11 -03:00
jenkins
c55c866b08 feat(hermes-triage-demo): least-privilege ConfigMap patch for in-process repair
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 22:25:52 -03:00
jenkins
fc0d41056d fix(scripts): dashboard renderer wrote outside the repo after the layout move
ROOT still used parents[1], which resolved to scripts/ once the renderer
moved into scripts/render/. Every --build run wrote a phantom
scripts/services/monitoring tree and silently left the real dashboards
untouched. Points at the repo root again and removes the stray tree.

Also adds Hermes triage panels to the Atlas Testing dashboard: open
escalations awaiting a human, automated actions succeeded, Hermes
diagnosis latency, actions by result, and incident state by job.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 21:41:02 -03:00
flux-bot
32eb26f17e chore(maintenance): automated image update 2026-08-06 00:39:46 +00:00
jenkins
2c11b87fdb fix(monitoring): Alertmanager must HELO with a FQDN for Mailu
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 21:33:11 -03:00
jenkins
84e9849e46 feat(hermes-triage): real-repo patch proposals + Alertmanager email escalation
- Ariadne: per-repo code config for metis, lesavka, soteria,
  bstein-dev-home and ariadne, each with its own base branch, source path
  prefixes and file suffixes so a proposal can only touch that repo's
  source tree
- Alertmanager: the only receiver was an empty "default", so every alert
  fired into a void. HermesTriageHumanRequired now routes to an email
  receiver via Mailu's in-cluster local-domain relay, with resolved
  notices; scoped to service=hermes-triage so nothing else mails yet.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 21:30:34 -03:00
flux-bot
a2aa9e7f67 chore(maintenance): automated image update 2026-08-06 00:15:26 +00:00
jenkins
f0c224e5f9 docs(hermes-triage): delivery record + enable Gitea issue filing config
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 21:01:50 -03:00
flux-bot
ba28379818 chore(maintenance): automated image update 2026-08-05 23:52:50 +00:00
jenkins
620f5b83d2 feat(hermes-triage): enable real-service triage across homegrown Jenkins jobs
Adds ariadne, lesavka, soteria, metis, ananke, pegasus, atlasbot,
bstein-dev-home and data-prepper to the autotriage allowlist. These get
detection, evidence, Hermes diagnosis and human-required escalation only:
no allowlisted repair action applies to them, so nothing is mutated.

Verified on lesavka #582 with the new console failure-region evidence:
classification coverage_quality_gate_failure, confidence 0.93, first
failed gate coverage (previously undetermined).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 20:31:44 -03:00
flux-bot
fdb2e8c411 chore(maintenance): automated image update 2026-08-05 23:28:21 +00:00
flux-bot
4b924b5033 chore(cassandra): automated image update 2026-08-05 23:22:31 +00:00
flux-bot
ed6077b2e4 chore(cassandra): automated image update 2026-08-05 23:18:23 +00:00
flux-bot
47569519a5 chore(cassandra): automated image update 2026-08-05 23:14:21 +00:00
jenkins
511e92ca60 fix(hermes-code): add branch discovery trait to multibranch source
Indexing succeeded but discovered no branches without an explicit
gitBranchDiscovery trait, so hermes-repair/* never got a validation build.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 20:14:01 -03:00
flux-bot
0e0d749fd5 chore(cassandra): automated image update 2026-08-05 23:13:18 +00:00
jenkins
a555fc0c96 feat(hermes-code): multibranch validation for hermes-repair/* proposal branches
Adds demo driver script and branch-level test gate so a Hermes-proposed
pull request carries a green build before human merge.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 19:46:30 -03:00
flux-bot
029779724c chore(maintenance): automated image update 2026-08-05 22:29:05 +00:00
jenkins
b4a45f1382 feat(hermes-code): demo repo Jenkins job + Ariadne code-path configuration
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 19:19:05 -03:00
jenkins
9b379daf00 revert(hermes-triage): restore action allowlist after alert rehearsal
Rehearsal confirmed: emptied allowlist -> incident 14 human_required
(action_not_allowlisted) -> HermesTriageHumanRequired FIRING in vmalert and
active in Alertmanager.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 19:03:28 -03:00
jenkins
0e9ee04cc4 docs(comms): resync runbook mirror
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 18:53:37 -03:00
jenkins
70d8f5f35f docs(hermes-triage): investigative-triage demonstration record + measured runbook timings
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 18:51:58 -03:00
jenkins
888819153c test(hermes-triage): temporarily empty action allowlist for alert-lifecycle rehearsal
Reverted immediately after the rehearsal confirms HermesTriageHumanRequired
fires and self-clears.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 18:51:58 -03:00
flux-bot
aa36b06b2b chore(maintenance): automated image update 2026-08-05 21:31:02 +00:00
jenkins
49aab8da32 feat(hermes-triage): enable autoremediation for the demo loop
Observe-mode rehearsal passed: incident hermes-triage-demo/9 diagnosed
correctly (0.99 confidence, known_demo_fixture_failure) and stopped only
at this kill switch. Scope stays: allowlisted job, one allowlisted
action, one action per incident, isolated namespace.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 17:56:46 -03:00
flux-bot
f32980f606 chore(maintenance): automated image update 2026-08-05 20:52:22 +00:00
jenkins
4048ed1de5 fix(hermes-triage-demo): restore groovy escape depths lost in ConfigMap rewrite
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 17:45:40 -03:00
jenkins
167ab91b60 refactor(hermes-triage-demo): ConfigMap fixture replaces Longhorn PVC
- Pipeline ensures the fixture ConfigMap and seeds unhealthy via patch-file
- Test-runner mounts the ConfigMap read-only; no storage attach involved
- New hermes-demo-repair SA + Role scoped to patching only the fixture CM
- Removes the fixture PVC (Flux prunes it)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 17:42:10 -03:00
jenkins
3e6783e44d fix(hermes-triage-demo): archive junit xml as artifact; junit plugin absent
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 17:32:32 -03:00
flux-bot
2068f3caba chore(maintenance): automated image update 2026-08-05 20:32:12 +00:00
flux-bot
3cffe30993 chore(maintenance): automated image update 2026-08-05 20:19:10 +00:00
jenkins
3787095d3c fix(hermes-triage-demo): pin test-runner to Longhorn-ready workers; add demo runbook
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 17:18:17 -03:00
flux-bot
239a98c5c9 chore(cassandra): automated image update 2026-08-05 20:05:00 +00:00
flux-bot
f648b3c31b chore(cassandra): automated image update 2026-08-05 20:02:00 +00:00
jenkins
8a5c93ac2f fix(hermes-triage-demo): correct groovy escape depth for newline join and shell JSON quotes
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 16:59:32 -03:00
flux-bot
48fff25915 chore(cassandra): automated image update 2026-08-05 19:57:05 +00:00
flux-bot
dc55fb79f8 chore(cassandra): automated image update 2026-08-05 19:56:58 +00:00
jenkins
3833e89545 fix(hermes-triage-demo): JCasC substitutes ${} at load; use concatenation-only pipeline
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 16:56:06 -03:00
jenkins
363adedd75 feat(hermes-triage): human-required alert rule + Ariadne autotriage config
- vmalert: hermes-triage.yaml alert group (HermesTriageHumanRequired on
  ariadne_hermes_triage_incident{status="human_required"}, 1m interval),
  wire Alertmanager notifier into vmalert-atlas-availability
- Ariadne deployment: hermes autotriage env (observe mode: autotriage on,
  autoremediation OFF), 1m schedule, allowlist hermes-triage-demo only,
  HERMES_API_KEY from optional hermes-api-server-key Secret
2026-08-05 16:51:48 -03:00
jenkins
b166f22830 fix(hermes-triage-demo): agent pod securityContext must match jnlp uid 1000
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 16:47:48 -03:00
jenkins
2bf90fa102 feat(hermes-triage-demo): isolated demo surface + Jenkins job + Hermes key seeding
- New hermes-triage-demo namespace with fixture PVC and RBAC scoped to
  Jenkins agent Job creation only
- JCasC pipelineJob hermes-triage-demo: SEED_FAILURE-parameterized fixture
  check running as a Kubernetes Job in the demo namespace, emitting the
  incident ID to pod stdout (Fluent Bit -> OpenSearch kube-*) and JUnit to
  Jenkins
- Hermes init container can seed API_SERVER_KEY in the persistent .env
  from an optional hermes-api-server-key Secret (no-op until it exists)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 16:42:37 -03:00
flux-bot
2427580936 chore(cassandra): automated image update 2026-08-05 19:27:31 +00:00
jenkins
f4097c6e59 Merge origin/main into layout migration
Reconciles the services layout migration with ~991 upstream commits:
- Remote content wins for cassandra/cassandra-auth, monitoring dashboards,
  vmalert availability rules, veles, vault auth script, dashboard render
  script and tests (request-v4 availability definition)
- Layout paths win for structure: keycloak/bstein-dev-home job dirs use
  bootstrap-jobs/validation-jobs; cassandra realm jobs live in
  cassandra-auth (removed keycloak duplicates)
- Union: applications CR list gains hermes-chat and cassandra
  image-automation
- Fixed post-migration paths in dashboard test module loader and
  hermes-access job header

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 16:26:36 -03:00
jenkins
0da9e4c82d refactor: restructure services layout, retire oceanus, add aether scaffolding
- Move flat service manifests into structured subdirs (apps/, bootstrap-jobs/,
  repair-jobs/, migration-jobs/, validation-jobs/, node-ops/, networking/)
- Retire oneoffs/ directories across services
- Remove oceanus cluster and its host roles; add aether cluster + terraform scaffolding
- Reorganize scripts/ into ops/, render/, sync/, manual-tests/
- Add Makefile with render/validate/test/flux targets and repo-structure tests
- Update flux-system application CRs to the new paths
- Add hermes-automated-triage-24h-plan knowledge doc (+ comms mirror)
- Refresh knowledge catalogs, dashboards, vmalert rules, quality contract

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 16:21:36 -03:00
flux-bot
0586613e67 chore(cassandra): automated image update 2026-08-05 18:08:35 +00:00
flux-bot
f554fb174f chore(cassandra): automated image update 2026-08-05 18:04:39 +00:00
flux-bot
cb9d00fc1b chore(cassandra): automated image update 2026-08-05 18:00:33 +00:00
jenkins
7bb6c831f6 security(portal): disable outbound SMTP 2026-08-05 15:00:10 -03:00
flux-bot
b90c66e0fb chore(cassandra): automated image update 2026-08-05 17:59:36 +00:00
flux-bot
456be51feb chore(maintenance): automated image update 2026-08-05 17:15:26 +00:00
flux-bot
48c5365dae chore(cassandra): automated image update 2026-08-05 15:33:40 +00:00
flux-bot
88881c5ad7 chore(cassandra): automated image update 2026-08-05 15:29:39 +00:00
flux-bot
719e4b40b4 chore(cassandra): automated image update 2026-08-05 15:25:40 +00:00
flux-bot
e2f7d7c16e chore(cassandra): automated image update 2026-08-05 15:24:41 +00:00
jenkins
ede91ce69a security(portal): disable public access requests 2026-08-05 12:12:49 -03:00
jenkins
9b8a29022d Revert "monitoring(network): organize Traefik traffic lanes"
This reverts commit eeec8b72dc885950dd116d6dc2ad6574c1cbe6b1.
2026-08-05 12:06:23 -03:00
jenkins
eeec8b72dc monitoring(network): organize Traefik traffic lanes 2026-08-05 12:02:51 -03:00
flux-bot
5b91159831 chore(cassandra): automated image update 2026-08-05 13:54:23 +00:00
flux-bot
faea58aa75 chore(cassandra): automated image update 2026-08-05 13:51:22 +00:00
flux-bot
b3af70afca chore(cassandra): automated image update 2026-08-05 13:47:21 +00:00
flux-bot
567ab34a2c chore(cassandra): automated image update 2026-08-05 13:46:22 +00:00
flux-bot
4dbbba20b6 chore(cassandra): automated image update 2026-08-05 11:10:58 +00:00
flux-bot
64b17f3ec5 chore(cassandra): automated image update 2026-08-05 11:06:59 +00:00
flux-bot
b3f34f5bf2 chore(cassandra): automated image update 2026-08-05 11:03:08 +00:00
flux-bot
f4e7e4528c chore(cassandra): automated image update 2026-08-05 11:02:54 +00:00
flux-bot
d880c86bd3 chore(cassandra): automated image update 2026-08-05 10:22:51 +00:00
flux-bot
0040376e86 chore(cassandra): automated image update 2026-08-05 10:09:49 +00:00
flux-bot
10bf39695f chore(cassandra): automated image update 2026-08-05 10:01:26 +00:00
flux-bot
559935f5b9 chore(cassandra): automated image update 2026-08-05 10:00:49 +00:00
flux-bot
c1b107240d chore(cassandra): automated image update 2026-08-05 07:45:39 +00:00
flux-bot
13e81f8d9f chore(cassandra): automated image update 2026-08-05 07:33:25 +00:00
flux-bot
786fdb70f3 chore(cassandra): automated image update 2026-08-05 07:25:20 +00:00
flux-bot
d5a9d86bfa chore(cassandra): automated image update 2026-08-05 07:24:25 +00:00
flux-bot
c147cb80fb chore(cassandra): automated image update 2026-08-05 05:47:05 +00:00
flux-bot
2b6475f062 chore(cassandra): automated image update 2026-08-05 05:36:04 +00:00
flux-bot
88510874bf chore(cassandra): automated image update 2026-08-05 05:26:13 +00:00
flux-bot
e5602d8d7f chore(cassandra): automated image update 2026-08-05 05:26:02 +00:00
flux-bot
e0f62ee0a2 chore(maintenance): automated image update 2026-08-05 05:15:46 +00:00
flux-bot
2667393b37 chore(cassandra): automated image update 2026-08-05 04:18:26 +00:00
flux-bot
067c3fcc5d chore(cassandra): automated image update 2026-08-05 04:07:31 +00:00
flux-bot
df0aeaac38 chore(cassandra): automated image update 2026-08-05 03:19:29 +00:00
flux-bot
5ad7317ffe chore(cassandra): automated image update 2026-08-05 03:19:19 +00:00
flux-bot
acedf1473f chore(cassandra): automated image update 2026-08-05 02:43:17 +00:00
flux-bot
13e2e978f9 chore(cassandra): automated image update 2026-08-05 02:33:21 +00:00
flux-bot
e6772ce47c chore(cassandra): automated image update 2026-08-05 02:25:22 +00:00
flux-bot
a3a9963c9f chore(cassandra): automated image update 2026-08-05 02:25:12 +00:00
jenkins
ea53bec75d monitoring: remove legacy availability series 2026-08-04 22:49:48 -03:00
flux-bot
1bff231569 chore(maintenance): automated image update 2026-08-05 01:40:46 +00:00
flux-bot
a547d301a7 chore(maintenance): automated image update 2026-08-05 01:40:41 +00:00
flux-bot
8d0ad52608 chore(maintenance): automated image update 2026-08-05 01:38:41 +00:00
flux-bot
5720c1638c chore(maintenance): automated image update 2026-08-05 01:37:42 +00:00
jenkins
663aa3e4f1 monitoring: retain daily availability through retries 2026-08-04 21:47:30 -03:00
jenkins
2691581cbc monitoring: publish availability outside query pool 2026-08-04 21:40:24 -03:00
jenkins
a8dda9815d monitoring: add daily availability rollups 2026-08-04 21:24:47 -03:00
jenkins
7c47a92610 monitoring: add compact availability fallback 2026-08-04 21:14:06 -03:00
jenkins
f724e72a96 monitoring: use request success for availability 2026-08-04 21:12:31 -03:00
jenkins
4906fb73d8 monitoring: measure gateway availability 2026-08-04 21:02:19 -03:00
flux-bot
27e80eec96 chore(cassandra): automated image update 2026-08-04 23:56:56 +00:00
flux-bot
d077b23370 chore(cassandra): automated image update 2026-08-04 23:47:01 +00:00
flux-bot
0a54db26ab chore(cassandra): automated image update 2026-08-04 23:38:58 +00:00
flux-bot
fbe334c8a7 chore(cassandra): automated image update 2026-08-04 23:38:50 +00:00
flux-bot
495a2f1248 chore(cassandra): automated image update 2026-08-04 22:48:51 +00:00
flux-bot
2a5086bd8d chore(cassandra): automated image update 2026-08-04 22:39:54 +00:00
flux-bot
bb40ecdb75 chore(cassandra): automated image update 2026-08-04 22:32:51 +00:00
flux-bot
f8ae54be56 chore(cassandra): automated image update 2026-08-04 22:31:43 +00:00
jenkins
302fe78681 monitoring: preserve serving availability history 2026-08-04 18:39:36 -03:00
jenkins
d3ddd246d3 vaultwarden: upgrade server to 1.37.0 2026-08-04 18:33:29 -03:00
flux-bot
4937f269e6 chore(cassandra): automated image update 2026-08-04 21:26:46 +00:00
flux-bot
94551d9436 chore(cassandra): automated image update 2026-08-04 21:19:43 +00:00
flux-bot
a3a2e391fb chore(cassandra): automated image update 2026-08-04 21:18:35 +00:00
jenkins
368de58769 monitoring: fall back to live serving state 2026-08-04 18:16:13 -03:00
jenkins
bbab7330ce monitoring: avoid unstable volume hosts 2026-08-04 18:06:43 -03:00
jenkins
0c108860ab vaultwarden: upgrade server to 1.36.0 2026-08-04 17:58:46 -03:00
flux-bot
21237cc0de chore(cassandra): automated image update 2026-08-04 20:52:36 +00:00
flux-bot
f9e27fa84e chore(cassandra): automated image update 2026-08-04 20:43:41 +00:00
flux-bot
996a60a8d0 chore(cassandra): automated image update 2026-08-04 20:35:40 +00:00
flux-bot
187cb905df chore(cassandra): automated image update 2026-08-04 20:34:32 +00:00
flux-bot
3afeafdf44 chore(cassandra): automated image update 2026-08-04 20:00:27 +00:00
flux-bot
9cc3e9c1ff chore(cassandra): automated image update 2026-08-04 19:53:31 +00:00
flux-bot
3fbe6065f7 chore(cassandra): automated image update 2026-08-04 19:43:35 +00:00
flux-bot
cba0afc732 chore(cassandra): automated image update 2026-08-04 19:33:32 +00:00
flux-bot
2ec60a45cd chore(cassandra): automated image update 2026-08-04 19:31:26 +00:00
flux-bot
ca57755cc0 chore(maintenance): automated image update 2026-08-04 17:15:44 +00:00
jenkins
301706b1c8 hermes: expose operator docs in file root 2026-08-04 13:22:18 -03:00
jenkins
5f9c313f0a monitoring: render test category zero state 2026-08-04 12:34:08 -03:00
flux-bot
c9f933cdc6 chore(cassandra): automated image update 2026-08-04 15:31:04 +00:00
jenkins
17b25c4429 monitoring: reload vmalert rules automatically 2026-08-04 12:24:38 -03:00
jenkins
b0599f4318 monitoring: prevent query pool starvation 2026-08-04 12:22:23 -03:00
flux-bot
59747de76d chore(cassandra): automated image update 2026-08-04 15:22:06 +00:00
flux-bot
33a9262638 chore(cassandra): automated image update 2026-08-04 15:14:06 +00:00
flux-bot
1ad3330a1e chore(cassandra): automated image update 2026-08-04 15:13:56 +00:00
flux-bot
93a860a007 chore(cassandra): automated image update 2026-08-04 14:36:58 +00:00
flux-bot
29993a9438 chore(cassandra): automated image update 2026-08-04 14:27:00 +00:00
flux-bot
fe9fc0128f chore(cassandra): automated image update 2026-08-04 14:20:00 +00:00
flux-bot
328b892973 chore(cassandra): automated image update 2026-08-04 14:18:50 +00:00
flux-bot
1c543e0727 chore(maintenance): automated image update 2026-08-04 13:48:08 +00:00
flux-bot
ca6b181233 chore(maintenance): automated image update 2026-08-04 13:48:04 +00:00
flux-bot
4ec7f63ef2 chore(maintenance): automated image update 2026-08-04 13:46:03 +00:00
flux-bot
fba7b12fa8 chore(maintenance): automated image update 2026-08-04 13:41:03 +00:00
flux-bot
cdd9aacc36 chore(cassandra): automated image update 2026-08-04 13:34:52 +00:00
flux-bot
3d5a7c80a7 chore(cassandra): automated image update 2026-08-04 13:25:55 +00:00
flux-bot
b234e44758 chore(cassandra): automated image update 2026-08-04 13:17:53 +00:00
flux-bot
093afdea83 chore(cassandra): automated image update 2026-08-04 13:17:44 +00:00
flux-bot
23b339d8f2 chore(cassandra): automated image update 2026-08-04 13:12:50 +00:00
flux-bot
832bf5b9f2 chore(cassandra): automated image update 2026-08-04 13:02:52 +00:00
flux-bot
66d597fa6d chore(cassandra): automated image update 2026-08-04 12:55:51 +00:00
flux-bot
c7825bb597 chore(cassandra): automated image update 2026-08-04 12:55:42 +00:00
flux-bot
46c5e3ea10 chore(cassandra): automated image update 2026-08-04 12:13:44 +00:00
flux-bot
33d9b9d2a5 chore(cassandra): automated image update 2026-08-04 12:12:46 +00:00
flux-bot
e8093ae77b chore(cassandra): automated image update 2026-08-04 12:09:46 +00:00
flux-bot
91f48ba7b1 chore(cassandra): automated image update 2026-08-04 12:09:39 +00:00
flux-bot
9b232d026d chore(cassandra): automated image update 2026-08-04 10:26:42 +00:00
flux-bot
262ad09311 chore(cassandra): automated image update 2026-08-04 10:24:44 +00:00
flux-bot
199e060662 chore(cassandra): automated image update 2026-08-04 10:21:45 +00:00
flux-bot
26a90d94b8 chore(cassandra): automated image update 2026-08-04 10:21:40 +00:00
flux-bot
984dda5f66 chore(cassandra): automated image update 2026-08-04 09:13:34 +00:00
flux-bot
fc9783abde chore(cassandra): automated image update 2026-08-04 09:12:35 +00:00
flux-bot
536eb9abc4 chore(cassandra): automated image update 2026-08-04 09:09:35 +00:00
flux-bot
f25039c454 chore(cassandra): automated image update 2026-08-04 09:09:31 +00:00
flux-bot
34f9701f1f chore(cassandra): automated image update 2026-08-04 07:55:22 +00:00
flux-bot
387889a324 chore(cassandra): automated image update 2026-08-04 07:53:35 +00:00
flux-bot
2a20a08cfc chore(cassandra): automated image update 2026-08-04 07:50:35 +00:00
flux-bot
7dc9b7f35c chore(cassandra): automated image update 2026-08-04 07:50:31 +00:00
flux-bot
71cc3cb931 chore(cassandra): automated image update 2026-08-04 07:19:13 +00:00
flux-bot
a6df2450c6 chore(cassandra): automated image update 2026-08-04 07:18:16 +00:00
flux-bot
2a03d2158b chore(cassandra): automated image update 2026-08-04 07:15:15 +00:00
flux-bot
a87259ac76 chore(cassandra): automated image update 2026-08-04 07:14:17 +00:00
flux-bot
b7eeed4eca chore(cassandra): automated image update 2026-08-04 05:44:03 +00:00
flux-bot
92545aced5 chore(cassandra): automated image update 2026-08-04 05:42:04 +00:00
flux-bot
613ffb6ade chore(cassandra): automated image update 2026-08-04 05:39:10 +00:00
flux-bot
aec40a3d4e chore(cassandra): automated image update 2026-08-04 05:39:04 +00:00
flux-bot
852bf111d8 chore(maintenance): automated image update 2026-08-04 05:15:01 +00:00
flux-bot
300398f0b2 chore(cassandra): automated image update 2026-08-04 04:32:54 +00:00
flux-bot
e7fa61ed2e chore(cassandra): automated image update 2026-08-04 04:31:54 +00:00
flux-bot
dea8a8423c chore(cassandra): automated image update 2026-08-04 04:28:52 +00:00
flux-bot
4a2e8d4763 chore(cassandra): automated image update 2026-08-04 04:27:54 +00:00
flux-bot
a18362fd9f chore(cassandra): automated image update 2026-08-04 03:28:46 +00:00
flux-bot
1695e4eee9 chore(cassandra): automated image update 2026-08-04 03:27:47 +00:00
flux-bot
4c870d5c71 chore(cassandra): automated image update 2026-08-04 03:24:45 +00:00
flux-bot
352f0757f8 chore(cassandra): automated image update 2026-08-04 03:23:46 +00:00
flux-bot
fd32160795 chore(cassandra): automated image update 2026-08-04 03:15:47 +00:00
flux-bot
63f4268801 chore(cassandra): automated image update 2026-08-04 03:14:45 +00:00
flux-bot
e8d0c85a26 chore(cassandra): automated image update 2026-08-04 03:11:45 +00:00
flux-bot
ef49c1daaf chore(cassandra): automated image update 2026-08-04 03:10:44 +00:00
flux-bot
df15f61c71 chore(cassandra): automated image update 2026-08-04 02:38:41 +00:00
flux-bot
18115291f8 chore(cassandra): automated image update 2026-08-04 02:36:41 +00:00
flux-bot
d99e027c25 chore(cassandra): automated image update 2026-08-04 02:33:45 +00:00
flux-bot
6b1d6e2597 chore(cassandra): automated image update 2026-08-04 02:33:40 +00:00
jenkins
7b99d12124 hermes: add operator mastery guide 2026-08-03 23:28:36 -03:00
flux-bot
92c2bbdb12 chore(cassandra): automated image update 2026-08-04 02:13:38 +00:00
flux-bot
0cb800a1d2 chore(cassandra): automated image update 2026-08-04 02:11:38 +00:00
flux-bot
945d27620d chore(cassandra): automated image update 2026-08-04 02:08:43 +00:00
flux-bot
eb1ad453ba chore(cassandra): automated image update 2026-08-04 02:08:38 +00:00
flux-bot
2cad5bb5a9 chore(maintenance): automated image update 2026-08-04 01:47:38 +00:00
flux-bot
8c85a18034 chore(maintenance): automated image update 2026-08-04 01:46:36 +00:00
flux-bot
bdb07f6119 chore(maintenance): automated image update 2026-08-04 01:44:37 +00:00
flux-bot
bca17e362f chore(maintenance): automated image update 2026-08-04 01:40:34 +00:00
jenkins
5dc71287c7 Deploy Cassandra 0.8.150 2026-08-03 22:18:39 -03:00
flux-bot
4e6b15dad7 chore(cassandra): automated image update 2026-08-04 01:16:31 +00:00
flux-bot
4da7887594 chore(cassandra): automated image update 2026-08-04 01:15:31 +00:00
flux-bot
288f91804a chore(cassandra): automated image update 2026-08-04 01:12:32 +00:00
flux-bot
ba2ba38e75 chore(cassandra): automated image update 2026-08-04 01:11:31 +00:00
jenkins
54b1e88c0f Deploy Cassandra 0.8.149 2026-08-03 21:19:25 -03:00
flux-bot
97bb4d4268 chore(cassandra): automated image update 2026-08-04 00:12:24 +00:00
flux-bot
3f8cd1fe0e chore(cassandra): automated image update 2026-08-04 00:10:25 +00:00
flux-bot
de10be679b chore(cassandra): automated image update 2026-08-04 00:07:27 +00:00
flux-bot
b1c9092b22 chore(cassandra): automated image update 2026-08-04 00:07:22 +00:00
jenkins
0babec4289 Deploy Cassandra 0.8.148 2026-08-03 20:43:49 -03:00
flux-bot
09a5831023 chore(cassandra): automated image update 2026-08-03 23:40:23 +00:00
flux-bot
704d8c0072 chore(cassandra): automated image update 2026-08-03 23:39:21 +00:00
flux-bot
924a58adc4 chore(cassandra): automated image update 2026-08-03 23:36:24 +00:00
flux-bot
21a2885abc chore(cassandra): automated image update 2026-08-03 23:36:19 +00:00
jenkins
e8facb6613 Deploy Cassandra 0.8.147 2026-08-03 19:08:37 -03:00
flux-bot
2aa213ab2c chore(cassandra): automated image update 2026-08-03 22:07:10 +00:00
flux-bot
dbc25a33e5 chore(cassandra): automated image update 2026-08-03 22:05:11 +00:00
flux-bot
dc09a453c6 chore(cassandra): automated image update 2026-08-03 22:02:15 +00:00
flux-bot
175ed86e38 chore(cassandra): automated image update 2026-08-03 22:02:09 +00:00
flux-bot
09f106b9fe chore(bstein-dev-home): automated image update 2026-08-03 21:52:13 +00:00
flux-bot
be202cbb47 chore(bstein-dev-home): automated image update 2026-08-03 21:52:09 +00:00
jenkins
dab75df58f Deploy Cassandra 0.8.146 2026-08-03 17:49:21 -03:00
flux-bot
5286c6ee29 chore(cassandra): automated image update 2026-08-03 20:47:01 +00:00
flux-bot
d2601c29a9 chore(cassandra): automated image update 2026-08-03 20:46:03 +00:00
flux-bot
0169426ad1 chore(cassandra): automated image update 2026-08-03 20:42:09 +00:00
flux-bot
e59048e8e9 chore(cassandra): automated image update 2026-08-03 20:42:03 +00:00
jenkins
ddb0d6f958 Deploy Cassandra 0.8.145 2026-08-03 16:55:04 -03:00
flux-bot
4b25df67b0 chore(cassandra): automated image update 2026-08-03 19:52:55 +00:00
flux-bot
72005ebaec chore(cassandra): automated image update 2026-08-03 19:40:57 +00:00
flux-bot
fc1e27998e chore(cassandra): automated image update 2026-08-03 19:37:56 +00:00
flux-bot
14278ca187 chore(cassandra): automated image update 2026-08-03 19:36:55 +00:00
jenkins
46146acd9a Deploy Cassandra 0.8.144 2026-08-03 16:18:38 -03:00
flux-bot
9347de6b4b chore(cassandra): automated image update 2026-08-03 19:14:51 +00:00
flux-bot
66ab8f9891 chore(cassandra): automated image update 2026-08-03 19:13:54 +00:00
flux-bot
9f3626940f chore(cassandra): automated image update 2026-08-03 19:10:50 +00:00
flux-bot
a60bdd7e89 chore(cassandra): automated image update 2026-08-03 19:09:52 +00:00
jenkins
95bc7b6f17 monitoring(grafana): use Texas timezone 2026-08-03 15:58:16 -03:00
jenkins
29a87dbfa8 Deploy Cassandra 0.8.143 2026-08-03 15:22:43 -03:00
flux-bot
9fff8dbcf1 chore(cassandra): automated image update 2026-08-03 18:10:45 +00:00
flux-bot
bad6ca6ef8 chore(cassandra): automated image update 2026-08-03 17:57:46 +00:00
flux-bot
ca93db2fcd chore(cassandra): automated image update 2026-08-03 17:54:42 +00:00
flux-bot
04d857fce2 chore(cassandra): automated image update 2026-08-03 17:53:44 +00:00
jenkins
dec2783621 Deploy Cassandra 0.8.142 2026-08-03 14:19:19 -03:00
flux-bot
078e9b3af4 chore(cassandra): automated image update 2026-08-03 17:16:40 +00:00
flux-bot
2c428db05e chore(cassandra): automated image update 2026-08-03 17:14:54 +00:00
flux-bot
c02a8dadd2 chore(maintenance): automated image update 2026-08-03 17:14:44 +00:00
flux-bot
b4d58eac67 chore(cassandra): automated image update 2026-08-03 17:10:45 +00:00
jenkins
78a1ba94e7 Deploy Cassandra 0.8.141 2026-08-03 13:13:54 -03:00
flux-bot
c4716e124d chore(cassandra): automated image update 2026-08-03 16:09:02 +00:00
flux-bot
add92ed780 chore(cassandra): automated image update 2026-08-03 16:07:04 +00:00
flux-bot
01a14cba20 chore(cassandra): automated image update 2026-08-03 16:04:00 +00:00
flux-bot
bf4eb1eb2f chore(cassandra): automated image update 2026-08-03 16:03:05 +00:00
jenkins
134fae7a3f Deploy Cassandra 0.8.140 2026-08-03 12:39:06 -03:00
flux-bot
2215794799 chore(cassandra): automated image update 2026-08-03 15:36:58 +00:00
flux-bot
0485c69504 chore(cassandra): automated image update 2026-08-03 15:35:01 +00:00
flux-bot
1cc56561ae chore(cassandra): automated image update 2026-08-03 15:31:57 +00:00
flux-bot
cc3fc2bba7 chore(cassandra): automated image update 2026-08-03 15:31:02 +00:00
jenkins
b656b7afd1 Deploy Cassandra 0.8.139 2026-08-03 11:27:34 -03:00
flux-bot
f53494e3db chore(cassandra): automated image update 2026-08-03 14:24:50 +00:00
flux-bot
e99806d8e8 chore(cassandra): automated image update 2026-08-03 14:23:52 +00:00
flux-bot
81c3e06763 chore(cassandra): automated image update 2026-08-03 14:20:54 +00:00
flux-bot
44189ac22d chore(cassandra): automated image update 2026-08-03 14:20:50 +00:00
flux-bot
ac0a7675e5 chore(maintenance): automated image update 2026-08-03 13:43:50 +00:00
flux-bot
2acf85de78 chore(maintenance): automated image update 2026-08-03 13:42:49 +00:00
flux-bot
421d234989 chore(maintenance): automated image update 2026-08-03 13:41:50 +00:00
flux-bot
e9f56c12f1 chore(maintenance): automated image update 2026-08-03 13:39:48 +00:00
jenkins
0d4a5b5b08 Deploy Cassandra 0.8.138 2026-08-03 10:13:39 -03:00
flux-bot
40187a7918 chore(cassandra): automated image update 2026-08-03 13:11:42 +00:00
flux-bot
e547545265 chore(cassandra): automated image update 2026-08-03 13:10:44 +00:00
flux-bot
2bd48836bf chore(cassandra): automated image update 2026-08-03 13:07:45 +00:00
flux-bot
cf61e4fe71 chore(cassandra): automated image update 2026-08-03 13:06:44 +00:00
jenkins
ffccf44831 Deploy Cassandra 0.8.137 2026-08-03 09:50:57 -03:00
flux-bot
45ce102316 chore(cassandra): automated image update 2026-08-03 12:48:40 +00:00
flux-bot
096776fb92 chore(cassandra): automated image update 2026-08-03 12:46:42 +00:00
flux-bot
1a36d1d228 chore(cassandra): automated image update 2026-08-03 12:43:44 +00:00
flux-bot
f1e81648a1 chore(cassandra): automated image update 2026-08-03 12:43:39 +00:00
jenkins
794cb9423b Deploy Cassandra 0.8.136 2026-08-03 08:39:56 -03:00
flux-bot
20cddc7296 chore(cassandra): automated image update 2026-08-03 11:31:31 +00:00
flux-bot
7979f9ed4d chore(cassandra): automated image update 2026-08-03 11:29:33 +00:00
flux-bot
8a3a47d4d3 chore(cassandra): automated image update 2026-08-03 11:26:30 +00:00
flux-bot
2e2c2b07af chore(cassandra): automated image update 2026-08-03 11:25:34 +00:00
jenkins
ce6d23ddfe Deploy Cassandra 0.8.135 2026-08-03 07:50:09 -03:00
flux-bot
983498b9b8 chore(cassandra): automated image update 2026-08-03 10:46:26 +00:00
flux-bot
0dd5ea9aae chore(cassandra): automated image update 2026-08-03 10:45:28 +00:00
flux-bot
8dcf71e8e9 chore(cassandra): automated image update 2026-08-03 10:42:24 +00:00
flux-bot
db25834362 chore(cassandra): automated image update 2026-08-03 10:41:32 +00:00
jenkins
8b255c43bd Deploy Cassandra 0.8.134 2026-08-03 06:51:09 -03:00
flux-bot
af0113d460 chore(cassandra): automated image update 2026-08-03 09:48:19 +00:00
flux-bot
5e1581e923 chore(cassandra): automated image update 2026-08-03 09:46:22 +00:00
flux-bot
8916ea9dc3 chore(bstein-dev-home): automated image update 2026-08-03 09:43:32 +00:00
flux-bot
d8e1a3633a chore(cassandra): automated image update 2026-08-03 09:43:25 +00:00
flux-bot
16f25f2f84 chore(cassandra): automated image update 2026-08-03 09:43:18 +00:00
flux-bot
d44d9856d0 chore(bstein-dev-home): automated image update 2026-08-03 09:42:24 +00:00
jenkins
b6ab86d9c0 Deploy Cassandra 0.8.133 2026-08-03 05:26:50 -03:00
flux-bot
da69bd96a9 chore(cassandra): automated image update 2026-08-03 08:25:10 +00:00
flux-bot
18c77569ac chore(cassandra): automated image update 2026-08-03 08:24:13 +00:00
flux-bot
9158481b8a chore(cassandra): automated image update 2026-08-03 08:20:14 +00:00
flux-bot
27f1b46747 chore(cassandra): automated image update 2026-08-03 08:20:08 +00:00
jenkins
001672ad82 Deploy Cassandra 0.8.132 2026-08-03 04:44:14 -03:00
flux-bot
424f86a817 chore(cassandra): automated image update 2026-08-03 07:43:05 +00:00
flux-bot
71bc0a6815 chore(cassandra): automated image update 2026-08-03 07:41:06 +00:00
flux-bot
a26228e456 chore(cassandra): automated image update 2026-08-03 07:38:08 +00:00
flux-bot
57a870ce2c chore(cassandra): automated image update 2026-08-03 07:38:04 +00:00
jenkins
c91ad76fdf docs(hermes): map custom service runtimes 2026-08-03 04:37:07 -03:00
jenkins
fdb74fa9f9 docs(hermes): publish Atlas triage proofs 2026-08-03 04:30:16 -03:00
flux-bot
951139f675 chore(maintenance): automated image update 2026-08-03 07:18:05 +00:00
jenkins
5823523250 fix(hermes): classify Veles as migration residue 2026-08-03 04:13:59 -03:00
jenkins
8db88d79f4 fix(monitoring): reload provisioned Grafana rules 2026-08-03 04:05:33 -03:00
jenkins
f6501e3d9e chore(cassandra): resuspend completed secret bootstrap 2026-08-03 04:02:30 -03:00
jenkins
abb9a39971 feat(hermes): package Atlas skill instructions 2026-08-03 03:59:00 -03:00
jenkins
e8f75f5cc9 feat(hermes): add actionable Atlas triage skills 2026-08-03 03:58:37 -03:00
jenkins
4bb1587be0 Deploy Cassandra 0.8.131 2026-08-03 02:46:36 -03:00
flux-bot
3b5602bf59 chore(cassandra): automated image update 2026-08-03 05:44:43 +00:00
flux-bot
53cdfbf2df chore(cassandra): automated image update 2026-08-03 05:43:41 +00:00
flux-bot
d93a72a483 chore(cassandra): automated image update 2026-08-03 05:39:51 +00:00
flux-bot
a0f94acad0 chore(cassandra): automated image update 2026-08-03 05:39:47 +00:00
flux-bot
8b06cf3609 chore(maintenance): automated image update 2026-08-03 05:14:36 +00:00
jenkins
012f58570f Deploy Cassandra 0.8.130 2026-08-03 01:29:02 -03:00
flux-bot
c75d8f19d4 chore(cassandra): automated image update 2026-08-03 04:27:49 +00:00
flux-bot
43bb73d0ce chore(cassandra): automated image update 2026-08-03 04:26:06 +00:00
flux-bot
aa4c114293 chore(cassandra): automated image update 2026-08-03 04:22:23 +00:00
flux-bot
227dd530e4 chore(cassandra): automated image update 2026-08-03 04:22:17 +00:00
flux-bot
1e017fd474 chore(maintenance): automated image update 2026-08-03 01:51:06 +00:00
flux-bot
100a0705c1 chore(maintenance): automated image update 2026-08-03 01:51:03 +00:00
flux-bot
a30377df42 chore(maintenance): automated image update 2026-08-03 01:49:02 +00:00
flux-bot
f841b066f3 chore(maintenance): automated image update 2026-08-03 01:44:02 +00:00
jenkins
c828595f4f Deploy Cassandra 0.8.129 2026-08-02 21:32:47 -03:00
flux-bot
5692b70ed0 chore(cassandra): automated image update 2026-08-03 00:30:24 +00:00
flux-bot
d501ba9961 chore(cassandra): automated image update 2026-08-03 00:28:43 +00:00
flux-bot
8d454b6caa chore(cassandra): automated image update 2026-08-03 00:25:51 +00:00
flux-bot
e319712a6f chore(cassandra): automated image update 2026-08-03 00:24:54 +00:00
jenkins
604f91cff3 monitoring(nodes): clamp CPU charts to physical range 2026-08-02 19:36:44 -03:00
jenkins
c0e2b5a2b9 fix(hermes): remove stale Veles proof 2026-08-02 19:04:14 -03:00
flux-bot
1051191da0 chore(maintenance): automated image update 2026-08-02 20:54:01 +00:00
jenkins
fcb6fd0171 feat(hermes): make cluster triage immediately usable 2026-08-02 17:26:58 -03:00
jenkins
fa21a760ef fix(hermes): report consumer access denial 2026-08-02 17:00:40 -03:00
jenkins
4fe6dba491 fix(hermes): isolate consumer sessions 2026-08-02 16:46:21 -03:00
jenkins
b08cca85d5 Deploy Cassandra 0.8.128 2026-08-02 16:22:06 -03:00
flux-bot
797fddaa30 chore(cassandra): automated image update 2026-08-02 19:19:40 +00:00
flux-bot
9594929b05 chore(cassandra): automated image update 2026-08-02 19:18:11 +00:00
flux-bot
d833ec69d3 chore(cassandra): automated image update 2026-08-02 19:15:20 +00:00
flux-bot
42d7c90884 chore(cassandra): automated image update 2026-08-02 19:15:16 +00:00
jenkins
b78e41cade fix(hermes): prevent stale dashboard sockets 2026-08-02 15:53:30 -03:00
jenkins
784d5fcaf8 Deploy Cassandra 0.8.127 2026-08-02 15:46:19 -03:00
flux-bot
e3677aa914 chore(cassandra): automated image update 2026-08-02 18:43:35 +00:00
flux-bot
8110e2862c chore(cassandra): automated image update 2026-08-02 18:42:07 +00:00
flux-bot
87cacfd28f chore(cassandra): automated image update 2026-08-02 18:39:15 +00:00
flux-bot
a96a35c144 chore(cassandra): automated image update 2026-08-02 18:39:12 +00:00
jenkins
249fe18b09 fix(hermes): reset consumer PTY session 2026-08-02 15:36:44 -03:00
jenkins
e864d1635a fix(hermes): stabilize consumer chat transport 2026-08-02 15:25:22 -03:00
jenkins
5661f9bd20 Deploy Cassandra 0.8.126 2026-08-02 14:23:14 -03:00
flux-bot
958233a167 chore(cassandra): automated image update 2026-08-02 17:17:55 +00:00
flux-bot
cc5fbda0b2 chore(cassandra): automated image update 2026-08-02 17:16:45 +00:00
jenkins
4385a29249 fix(hermes): stabilize operator sessions 2026-08-02 14:15:14 -03:00
flux-bot
c3be75ec3f chore(maintenance): automated image update 2026-08-02 17:14:47 +00:00
flux-bot
a28a14a7ae chore(cassandra): automated image update 2026-08-02 17:12:45 +00:00
flux-bot
ff86ce5206 chore(cassandra): automated image update 2026-08-02 17:12:39 +00:00
jenkins
bd6dca98ba Deploy Cassandra 0.8.125 2026-08-02 13:19:19 -03:00
flux-bot
840b5900de chore(cassandra): automated image update 2026-08-02 16:15:00 +00:00
flux-bot
20c0973b1f chore(cassandra): automated image update 2026-08-02 16:14:15 +00:00
flux-bot
6da663d066 chore(cassandra): automated image update 2026-08-02 16:09:02 +00:00
flux-bot
0f939c1d86 chore(cassandra): automated image update 2026-08-02 16:08:57 +00:00
jenkins
045f17a095 docs(hermes): align training with Codex-first routing 2026-08-02 12:25:51 -03:00
flux-bot
8bd8c94312 chore(cassandra): automated image update 2026-08-02 14:59:27 +00:00
flux-bot
9e09b2346c chore(cassandra): automated image update 2026-08-02 14:57:27 +00:00
flux-bot
9839ebb803 chore(cassandra): automated image update 2026-08-02 14:54:27 +00:00
flux-bot
7c66020338 chore(cassandra): automated image update 2026-08-02 14:53:27 +00:00
flux-bot
017ebff9b1 chore(maintenance): automated image update 2026-08-02 13:31:33 +00:00
flux-bot
8afa72a54f chore(maintenance): automated image update 2026-08-02 13:31:29 +00:00
flux-bot
c9c1aeed0c chore(maintenance): automated image update 2026-08-02 13:29:28 +00:00
flux-bot
60e5f1d6bf chore(maintenance): automated image update 2026-08-02 13:27:28 +00:00
jenkins
cb05de8661 Deploy Cassandra 0.8.123 2026-08-02 09:28:52 -03:00
flux-bot
ee5c3882f8 chore(cassandra): automated image update 2026-08-02 12:24:06 +00:00
flux-bot
e0788cd8e3 chore(cassandra): automated image update 2026-08-02 12:21:21 +00:00
flux-bot
af13533606 chore(cassandra): automated image update 2026-08-02 12:18:23 +00:00
flux-bot
0696adbdbe chore(cassandra): automated image update 2026-08-02 12:18:18 +00:00
jenkins
f4d6b50700 ai(hermes): prefer Codex and trim default prompts 2026-08-02 08:54:04 -03:00
jenkins
ec8c3d5cda Deploy Cassandra 0.8.122 2026-08-02 07:40:32 -03:00
flux-bot
8337a6889d chore(cassandra): automated image update 2026-08-02 10:38:10 +00:00
flux-bot
1e18721584 chore(cassandra): automated image update 2026-08-02 10:31:42 +00:00
flux-bot
da8d1b3cea chore(cassandra): automated image update 2026-08-02 10:20:44 +00:00
flux-bot
2589a2d471 chore(cassandra): automated image update 2026-08-02 10:20:20 +00:00
flux-bot
0e5e66d5d0 chore(bstein-dev-home): automated image update 2026-08-02 09:40:38 +00:00
flux-bot
dd5fd7bfdd chore(bstein-dev-home): automated image update 2026-08-02 09:39:49 +00:00
jenkins
e026b34b15 Deploy Cassandra 0.8.121 2026-08-02 06:26:42 -03:00
flux-bot
9b99f30397 chore(cassandra): automated image update 2026-08-02 09:16:58 +00:00
flux-bot
30dc85cb13 chore(cassandra): automated image update 2026-08-02 09:15:58 +00:00
flux-bot
69faf5873c chore(cassandra): automated image update 2026-08-02 09:11:57 +00:00
flux-bot
4f6fc2bba5 chore(cassandra): automated image update 2026-08-02 09:10:57 +00:00
flux-bot
228c8b78a6 chore(cassandra): automated image update 2026-08-02 08:39:31 +00:00
flux-bot
065f9d7f67 chore(cassandra): automated image update 2026-08-02 08:37:30 +00:00
flux-bot
0c4f9bee5e chore(cassandra): automated image update 2026-08-02 08:34:27 +00:00
flux-bot
eaf0bd93a8 chore(cassandra): automated image update 2026-08-02 08:34:23 +00:00
flux-bot
fa9f99cd17 chore(cassandra): automated image update 2026-08-02 08:32:05 +00:00
jenkins
8f12670a58 chore(cassandra): deploy 0.8.120 2026-08-02 05:31:35 -03:00
jenkins
7659fc0086 Deploy Cassandra 0.8.119 2026-08-02 04:48:53 -03:00
flux-bot
9f34ae49f1 chore(cassandra): automated image update 2026-08-02 07:46:47 +00:00
flux-bot
66668fdfef chore(cassandra): automated image update 2026-08-02 07:45:47 +00:00
flux-bot
f8d38944f0 chore(cassandra): automated image update 2026-08-02 07:41:46 +00:00
flux-bot
1250072ac8 chore(cassandra): automated image update 2026-08-02 07:40:39 +00:00
jenkins
82b8a1c899 monitoring(gpu): attribute Jetson activity by allocation 2026-08-02 04:26:33 -03:00
jenkins
bc4d7021a8 Deploy Cassandra 0.8.118 2026-08-02 04:22:49 -03:00
flux-bot
b7691f5642 chore(cassandra): automated image update 2026-08-02 07:19:06 +00:00
flux-bot
ab27e69de5 chore(cassandra): automated image update 2026-08-02 07:17:37 +00:00
flux-bot
563758f151 chore(cassandra): automated image update 2026-08-02 07:14:32 +00:00
flux-bot
a5bd7a804a chore(cassandra): automated image update 2026-08-02 07:14:23 +00:00
jenkins
ef75c66314 docs(hermes): ground OpenClaw history in retained evidence 2026-08-02 04:12:07 -03:00
jenkins
96edd28b53 ai(hermes): include operator proof sprint 2026-08-02 04:01:40 -03:00
jenkins
4991493d3b ai(hermes): add operator guide and current GPU shares 2026-08-02 03:59:32 -03:00
jenkins
2fe327c7eb monitoring(gpu): use process-level pod attribution 2026-08-02 03:31:02 -03:00
jenkins
204ad44ff7 Deploy Cassandra 0.8.117 2026-08-02 03:26:19 -03:00
flux-bot
efd9dab86b chore(cassandra): automated image update 2026-08-02 06:25:25 +00:00
flux-bot
a5865fe7b3 chore(cassandra): automated image update 2026-08-02 06:24:25 +00:00
jenkins
4d5aeeb1f2 ai(hermes): steer mastery labs to Codex 2026-08-02 03:22:13 -03:00
flux-bot
7ab981be1d chore(cassandra): automated image update 2026-08-02 06:21:21 +00:00
flux-bot
3c507007f0 chore(cassandra): automated image update 2026-08-02 06:20:21 +00:00
jenkins
b324091939 ai(hermes-chat): permit Atlas OIDC egress 2026-08-02 03:18:34 -03:00
jenkins
e794ccf254 monitoring(gpu): report time-weighted namespace usage 2026-08-02 03:08:20 -03:00
jenkins
26a5dde12d ai(hermes-chat): allow read-only API endpoint access 2026-08-02 03:08:20 -03:00
jenkins
3cd147538f docs(hermes): correct GPU ownership reference 2026-08-02 02:51:35 -03:00
jenkins
85c5bfe9f4 ai(hermes): add operator mastery lab 2026-08-02 02:47:55 -03:00
jenkins
1494e76ccd ai(hermes): avoid unstable titan-18 placement 2026-08-02 02:35:39 -03:00
jenkins
ddb609dac7 ai(hermes): add isolated user chat instance 2026-08-02 02:31:01 -03:00
flux-bot
2aa0fbea09 chore(maintenance): automated image update 2026-08-02 05:15:26 +00:00
jenkins
1164ecc42c Deploy Cassandra 0.8.116 2026-08-02 02:04:53 -03:00
flux-bot
8776e9868c chore(cassandra): automated image update 2026-08-02 05:01:17 +00:00
flux-bot
d3653a7a03 chore(cassandra): automated image update 2026-08-02 04:59:10 +00:00
flux-bot
ad8484a568 chore(cassandra): automated image update 2026-08-02 04:56:09 +00:00
flux-bot
0ff61986f9 chore(cassandra): automated image update 2026-08-02 04:55:10 +00:00
flux-bot
5f832c3ee2 chore(cassandra): automated image update 2026-08-02 03:28:21 +00:00
flux-bot
5faf950afe chore(cassandra): automated image update 2026-08-02 03:26:13 +00:00
flux-bot
ae5970c664 chore(cassandra): automated image update 2026-08-02 03:23:15 +00:00
flux-bot
6ecad64706 chore(cassandra): automated image update 2026-08-02 03:23:09 +00:00
jenkins
267c7f00a5 ai(hermes): harden Atlas triage evidence guardrails 2026-08-01 23:18:49 -03:00
jenkins
aaa114eb60 gpu(titan-24): avoid unhealthy gate node 2026-08-01 22:59:29 -03:00
jenkins
a3f116dc35 Deploy Cassandra 0.8.114 2026-08-01 22:54:09 -03:00
jenkins
be91902a01 gpu(titan-24): add Hermes local-first fallback handoff 2026-08-01 22:50:58 -03:00
flux-bot
d58aa8e7e8 chore(cassandra): automated image update 2026-08-02 01:49:34 +00:00
flux-bot
f8c085e9a5 chore(maintenance): automated image update 2026-08-02 01:49:29 +00:00
flux-bot
c9c834c036 chore(cassandra): automated image update 2026-08-02 01:48:24 +00:00
flux-bot
9dbe8fec9d chore(maintenance): automated image update 2026-08-02 01:48:10 +00:00
flux-bot
8966552a1b chore(maintenance): automated image update 2026-08-02 01:46:17 +00:00
flux-bot
a7c409bc2e chore(cassandra): automated image update 2026-08-02 01:44:21 +00:00
flux-bot
624aa1e8c9 chore(maintenance): automated image update 2026-08-02 01:44:12 +00:00
flux-bot
0d8167b26d chore(bstein-dev-home): automated image update 2026-08-02 01:43:28 +00:00
flux-bot
39de2cdd7d chore(bstein-dev-home): automated image update 2026-08-02 01:42:02 +00:00
flux-bot
51527532a1 chore(maintenance): automated image update 2026-08-02 01:39:17 +00:00
jenkins
66055abe8b Deploy Cassandra 0.8.113 2026-08-01 20:41:10 -03:00
flux-bot
2a15f3ea8e chore(cassandra): automated image update 2026-08-01 23:39:57 +00:00
flux-bot
aae4452a19 chore(cassandra): automated image update 2026-08-01 23:38:57 +00:00
flux-bot
56bf928a07 chore(cassandra): automated image update 2026-08-01 23:33:45 +00:00
flux-bot
933259d3e5 chore(cassandra): automated image update 2026-08-01 23:33:40 +00:00
jenkins
0ccdcdce6f Deploy Cassandra 0.8.112 2026-08-01 19:50:13 -03:00
flux-bot
66b72b4d67 chore(cassandra): automated image update 2026-08-01 22:46:09 +00:00
flux-bot
d394ef6e39 chore(cassandra): automated image update 2026-08-01 22:45:09 +00:00
flux-bot
c799941743 chore(cassandra): automated image update 2026-08-01 22:41:01 +00:00
flux-bot
1055896026 chore(cassandra): automated image update 2026-08-01 22:40:01 +00:00
jenkins
efdb385a56 Deploy Cassandra 0.8.111 2026-08-01 19:19:29 -03:00
flux-bot
d09d9d37d2 chore(cassandra): automated image update 2026-08-01 22:15:25 +00:00
flux-bot
2a11cce00c chore(cassandra): automated image update 2026-08-01 22:13:18 +00:00
flux-bot
1660acf42f chore(cassandra): automated image update 2026-08-01 22:09:03 +00:00
flux-bot
c7f379c913 chore(cassandra): automated image update 2026-08-01 22:08:03 +00:00
flux-bot
969152cb8b chore(bstein-dev-home): automated image update 2026-08-01 21:48:51 +00:00
flux-bot
6155e6b791 chore(bstein-dev-home): automated image update 2026-08-01 21:46:50 +00:00
jenkins
7847956a49 Deploy Cassandra 0.8.110 2026-08-01 17:28:05 -03:00
flux-bot
577d8d6e70 chore(cassandra): automated image update 2026-08-01 20:24:52 +00:00
flux-bot
09019ae1a2 chore(cassandra): automated image update 2026-08-01 20:23:51 +00:00
flux-bot
91a7e5c286 chore(cassandra): automated image update 2026-08-01 20:19:55 +00:00
flux-bot
e450985bea chore(cassandra): automated image update 2026-08-01 20:19:51 +00:00
jenkins
ab28d19533 Deploy Cassandra 0.8.109 workers 2026-08-01 15:59:11 -03:00
flux-bot
f981951a19 chore(cassandra): automated image update 2026-08-01 18:56:05 +00:00
flux-bot
5a69fa3618 chore(cassandra): automated image update 2026-08-01 18:55:01 +00:00
flux-bot
c8e3b0b7be chore(cassandra): automated image update 2026-08-01 18:50:50 +00:00
flux-bot
64c5f6c989 chore(cassandra): automated image update 2026-08-01 18:50:47 +00:00
flux-bot
d8bb993a81 chore(cassandra): automated image update 2026-08-01 18:45:40 +00:00
flux-bot
6b4eb82119 chore(cassandra): automated image update 2026-08-01 18:42:36 +00:00
flux-bot
6e75a7ba19 chore(cassandra): automated image update 2026-08-01 18:38:41 +00:00
flux-bot
a63ef93ac8 chore(cassandra): automated image update 2026-08-01 18:37:35 +00:00
flux-bot
9587375354 chore(bstein-dev-home): automated image update 2026-08-01 18:30:52 +00:00
flux-bot
3adffb4422 chore(bstein-dev-home): automated image update 2026-08-01 18:28:38 +00:00
jenkins
4957ad143a Deploy Cassandra 0.8.107 guide jobs fix 2026-08-01 14:19:05 -03:00
flux-bot
8003c9186c chore(maintenance): automated image update 2026-08-01 17:15:15 +00:00
flux-bot
9ddb43f30e chore(cassandra): automated image update 2026-08-01 17:13:55 +00:00
flux-bot
05a947ab60 chore(cassandra): automated image update 2026-08-01 17:10:15 +00:00
flux-bot
5a45b47ecc chore(cassandra): automated image update 2026-08-01 17:08:35 +00:00
flux-bot
a2ed5e78c6 chore(cassandra): automated image update 2026-08-01 15:45:35 +00:00
flux-bot
1478deed87 chore(cassandra): automated image update 2026-08-01 15:42:55 +00:00
jenkins
4e65e87999 Deploy Cassandra 0.8.106 generator fixes 2026-08-01 12:41:50 -03:00
flux-bot
f278530d05 chore(cassandra): automated image update 2026-08-01 15:35:13 +00:00
flux-bot
0e91104133 chore(cassandra): automated image update 2026-08-01 15:34:17 +00:00
flux-bot
09c9bf0778 chore(cassandra): automated image update 2026-08-01 15:34:13 +00:00
jenkins
b3ab7ffe1b Deploy Cassandra 0.8.105 worker versions 2026-08-01 11:54:55 -03:00
flux-bot
a0ea6047c5 chore(cassandra): automated image update 2026-08-01 14:48:16 +00:00
flux-bot
67e3251b51 chore(cassandra): automated image update 2026-08-01 14:47:16 +00:00
flux-bot
b794f21a60 chore(cassandra): automated image update 2026-08-01 14:44:14 +00:00
flux-bot
4aaaeced7a chore(cassandra): automated image update 2026-08-01 14:42:00 +00:00
jenkins
c4600246dd Deploy Cassandra 0.8.104 workers 2026-08-01 11:14:05 -03:00
flux-bot
87ee1f3d2b chore(cassandra): automated image update 2026-08-01 14:12:10 +00:00
flux-bot
d9e0de657c chore(cassandra): automated image update 2026-08-01 14:11:10 +00:00
flux-bot
dc69f18cab chore(cassandra): automated image update 2026-08-01 14:08:06 +00:00
flux-bot
ed988e5103 chore(cassandra): automated image update 2026-08-01 13:56:35 +00:00
flux-bot
5c1d19aed4 chore(maintenance): automated image update 2026-08-01 13:48:28 +00:00
flux-bot
b985fdd9ee chore(maintenance): automated image update 2026-08-01 13:48:24 +00:00
flux-bot
f30ac8ad43 chore(maintenance): automated image update 2026-08-01 13:43:19 +00:00
flux-bot
343b132aca chore(maintenance): automated image update 2026-08-01 13:41:18 +00:00
2367e8e6ae Deploy Cassandra generator 0.8.103 2026-08-01 10:21:53 -03:00
flux-bot
495c704277 chore(cassandra): automated image update 2026-08-01 13:04:33 +00:00
flux-bot
087a03f1ef chore(cassandra): automated image update 2026-08-01 13:04:29 +00:00
flux-bot
d9914d12ce chore(cassandra): automated image update 2026-08-01 13:01:30 +00:00
flux-bot
94f3c39e96 chore(cassandra): automated image update 2026-08-01 13:01:26 +00:00
2d94bb1422 Deploy Cassandra generator 0.8.102 2026-08-01 09:45:16 -03:00
flux-bot
551227d0e3 chore(cassandra): automated image update 2026-08-01 12:42:57 +00:00
flux-bot
da925e9205 chore(cassandra): automated image update 2026-08-01 12:42:53 +00:00
flux-bot
53468524a5 chore(cassandra): automated image update 2026-08-01 12:38:55 +00:00
flux-bot
44fef6e556 chore(cassandra): automated image update 2026-08-01 12:38:51 +00:00
flux-bot
20fcb04b34 chore(cassandra): automated image update 2026-08-01 12:17:08 +00:00
97e2af6db6 Deploy Cassandra generator 0.8.101 2026-08-01 09:15:37 -03:00
flux-bot
24e4dd8690 chore(cassandra): automated image update 2026-08-01 12:14:07 +00:00
flux-bot
c62542390e chore(cassandra): automated image update 2026-08-01 12:13:11 +00:00
flux-bot
bbbdfc585b chore(cassandra): automated image update 2026-08-01 12:13:06 +00:00
d4db6867ba Deploy Cassandra generator 0.8.100 2026-08-01 08:50:36 -03:00
flux-bot
6ac1acc6e8 chore(cassandra): automated image update 2026-08-01 11:48:06 +00:00
flux-bot
83a739e745 chore(cassandra): automated image update 2026-08-01 11:47:10 +00:00
flux-bot
13b8888d8c chore(cassandra): automated image update 2026-08-01 11:45:06 +00:00
flux-bot
4415a63113 chore(cassandra): automated image update 2026-08-01 11:44:16 +00:00
a782f173ff Deploy Cassandra 0.8.99 generator repairs 2026-08-01 07:27:32 -03:00
flux-bot
d0f85b1d23 chore(cassandra): automated image update 2026-08-01 10:21:25 +00:00
flux-bot
b899bd81f0 chore(cassandra): automated image update 2026-08-01 10:19:20 +00:00
flux-bot
211d6e2a43 chore(cassandra): automated image update 2026-08-01 10:16:23 +00:00
flux-bot
f55529dda9 chore(cassandra): automated image update 2026-08-01 10:16:19 +00:00
jenkins
885b9850a2 Deploy Cassandra 0.8.98 generator routing 2026-08-01 06:01:45 -03:00
flux-bot
a19c75a74b chore(cassandra): automated image update 2026-08-01 08:58:37 +00:00
flux-bot
cd39487e1b chore(cassandra): automated image update 2026-08-01 08:55:36 +00:00
flux-bot
8b807919aa chore(cassandra): automated image update 2026-08-01 08:50:33 +00:00
flux-bot
f2faf3d78b chore(cassandra): automated image update 2026-08-01 08:49:33 +00:00
jenkins
bc9475e53f Extend Cassandra generator worker deadline 2026-08-01 04:54:13 -03:00
jenkins
3f5e7c520b Deploy Cassandra 0.8.97 2026-08-01 04:45:57 -03:00
flux-bot
127f447251 chore(cassandra): automated image update 2026-08-01 07:43:10 +00:00
flux-bot
21e73c3ae0 chore(cassandra): automated image update 2026-08-01 07:41:10 +00:00
flux-bot
a139e304b0 chore(cassandra): automated image update 2026-08-01 07:38:23 +00:00
flux-bot
28ac30356d chore(cassandra): automated image update 2026-08-01 07:38:09 +00:00
flux-bot
3b1557a983 chore(bstein-dev-home): automated image update 2026-08-01 06:48:28 +00:00
flux-bot
6b50c87142 chore(bstein-dev-home): automated image update 2026-08-01 06:46:24 +00:00
jenkins
5b8e935ef7 Deploy Cassandra 0.8.96 2026-08-01 03:05:57 -03:00
flux-bot
8b832e8795 chore(cassandra): automated image update 2026-08-01 06:03:50 +00:00
flux-bot
6049f43e76 chore(cassandra): automated image update 2026-08-01 06:01:50 +00:00
flux-bot
e108f240a2 chore(cassandra): automated image update 2026-08-01 05:58:48 +00:00
flux-bot
ce7f041141 chore(cassandra): automated image update 2026-08-01 05:57:49 +00:00
flux-bot
92387d7e74 chore(maintenance): automated image update 2026-08-01 05:14:44 +00:00
jenkins
0a9d825732 Deploy Cassandra 0.8.95 2026-08-01 01:47:17 -03:00
flux-bot
f4d7344f48 chore(cassandra): automated image update 2026-08-01 04:44:41 +00:00
flux-bot
d4ab8aa112 chore(cassandra): automated image update 2026-08-01 04:42:38 +00:00
flux-bot
a28e0c80c2 chore(cassandra): automated image update 2026-08-01 04:38:33 +00:00
flux-bot
0a4757ebc7 chore(cassandra): automated image update 2026-08-01 04:37:33 +00:00
flux-bot
f35c7e7bef chore(maintenance): automated image update 2026-08-01 01:46:38 +00:00
flux-bot
c27f73d5ce chore(maintenance): automated image update 2026-08-01 01:45:24 +00:00
flux-bot
12d0621f20 chore(maintenance): automated image update 2026-08-01 01:41:36 +00:00
flux-bot
c5fa5d3857 chore(maintenance): automated image update 2026-08-01 01:38:23 +00:00
flux-bot
e3a8b0dd5c chore(bstein-dev-home): automated image update 2026-07-31 21:49:43 +00:00
flux-bot
7dc07efd82 chore(bstein-dev-home): automated image update 2026-07-31 21:46:38 +00:00
flux-bot
cbd31473d0 chore(maintenance): automated image update 2026-07-31 17:15:05 +00:00
flux-bot
cff0c6a5bf chore(maintenance): automated image update 2026-07-31 13:47:11 +00:00
flux-bot
84accc0e86 chore(maintenance): automated image update 2026-07-31 13:47:05 +00:00
flux-bot
0320bcc971 chore(maintenance): automated image update 2026-07-31 13:42:00 +00:00
flux-bot
fe92a90275 chore(maintenance): automated image update 2026-07-31 13:40:00 +00:00
flux-bot
a4b346e42c chore(bstein-dev-home): automated image update 2026-07-31 09:48:58 +00:00
flux-bot
f8a242265d chore(bstein-dev-home): automated image update 2026-07-31 09:45:58 +00:00
flux-bot
f492ffc61f chore(maintenance): automated image update 2026-07-31 05:15:39 +00:00
flux-bot
0980c228d4 chore(maintenance): automated image update 2026-07-31 01:46:42 +00:00
flux-bot
2d1bbb6f8b chore(maintenance): automated image update 2026-07-31 01:46:37 +00:00
flux-bot
a26be06399 chore(maintenance): automated image update 2026-07-31 01:41:33 +00:00
flux-bot
7d2c023cb5 chore(maintenance): automated image update 2026-07-31 01:39:33 +00:00
flux-bot
e337a9cf6f chore(bstein-dev-home): automated image update 2026-07-30 21:50:20 +00:00
flux-bot
3115424774 chore(bstein-dev-home): automated image update 2026-07-30 21:47:17 +00:00
flux-bot
0679cae98a chore(maintenance): automated image update 2026-07-30 17:14:29 +00:00
flux-bot
e009ed8de5 chore(cassandra): automated image update 2026-07-30 15:28:01 +00:00
flux-bot
6560e0a866 chore(cassandra): automated image update 2026-07-30 15:27:01 +00:00
flux-bot
68a9a2d919 chore(cassandra): automated image update 2026-07-30 15:23:50 +00:00
flux-bot
b82cb62e2b chore(cassandra): automated image update 2026-07-30 15:22:48 +00:00
flux-bot
4eed51546b chore(cassandra): automated image update 2026-07-30 14:57:07 +00:00
flux-bot
51a5d5cc32 chore(cassandra): automated image update 2026-07-30 14:54:35 +00:00
flux-bot
082bffc1ee chore(cassandra): automated image update 2026-07-30 14:50:18 +00:00
flux-bot
368ec8f1de chore(cassandra): automated image update 2026-07-30 14:50:13 +00:00
flux-bot
443b81683c chore(cassandra): automated image update 2026-07-30 14:14:37 +00:00
flux-bot
6db32c4ff6 chore(cassandra): automated image update 2026-07-30 14:13:24 +00:00
flux-bot
2e50e73949 chore(cassandra): automated image update 2026-07-30 14:08:28 +00:00
flux-bot
72c2a83ddd chore(cassandra): automated image update 2026-07-30 14:08:15 +00:00
flux-bot
307bed2730 chore(maintenance): automated image update 2026-07-30 13:46:28 +00:00
flux-bot
a67312837c chore(maintenance): automated image update 2026-07-30 13:45:29 +00:00
flux-bot
dcbcaba0e8 chore(maintenance): automated image update 2026-07-30 13:41:27 +00:00
flux-bot
ca8959a5d6 chore(maintenance): automated image update 2026-07-30 13:39:27 +00:00
flux-bot
6dc3e460cb chore(cassandra): automated image update 2026-07-30 13:37:13 +00:00
flux-bot
2ccc3788b0 chore(cassandra): automated image update 2026-07-30 13:35:11 +00:00
flux-bot
9d3a7b339d chore(cassandra): automated image update 2026-07-30 13:31:54 +00:00
flux-bot
50838e8cc2 chore(cassandra): automated image update 2026-07-30 13:31:48 +00:00
flux-bot
8316bb469a chore(cassandra): automated image update 2026-07-30 12:34:39 +00:00
flux-bot
3e1cc10aab chore(cassandra): automated image update 2026-07-30 12:32:39 +00:00
flux-bot
9c70fc65cb chore(cassandra): automated image update 2026-07-30 12:29:44 +00:00
flux-bot
321d9daf58 chore(cassandra): automated image update 2026-07-30 12:29:38 +00:00
flux-bot
1b43caf867 chore(cassandra): automated image update 2026-07-30 12:00:47 +00:00
flux-bot
d2d3d1d46c chore(cassandra): automated image update 2026-07-30 11:59:47 +00:00
flux-bot
63effd32ac chore(cassandra): automated image update 2026-07-30 11:56:44 +00:00
flux-bot
d0088bdf23 chore(cassandra): automated image update 2026-07-30 11:56:39 +00:00
flux-bot
d344ff44d4 chore(cassandra): automated image update 2026-07-30 11:20:02 +00:00
flux-bot
d736645535 chore(cassandra): automated image update 2026-07-30 11:17:47 +00:00
flux-bot
819a7018d4 chore(cassandra): automated image update 2026-07-30 11:15:44 +00:00
flux-bot
5c802b2dc8 chore(cassandra): automated image update 2026-07-30 11:14:44 +00:00
flux-bot
955f3096ac chore(cassandra): automated image update 2026-07-30 10:40:10 +00:00
flux-bot
6d59562879 chore(cassandra): automated image update 2026-07-30 10:38:10 +00:00
flux-bot
8458d454ad chore(cassandra): automated image update 2026-07-30 10:35:07 +00:00
flux-bot
3b1873f2f0 chore(cassandra): automated image update 2026-07-30 10:34:08 +00:00
flux-bot
d8ff568102 chore(bstein-dev-home): automated image update 2026-07-30 09:49:10 +00:00
flux-bot
fdd55afe2d chore(bstein-dev-home): automated image update 2026-07-30 09:46:49 +00:00
jenkins
8474c2f9db Deploy Cassandra 0.8.86 2026-07-30 06:41:05 -03:00
flux-bot
064be39440 chore(cassandra): automated image update 2026-07-30 09:38:19 +00:00
flux-bot
b809f3c2fd chore(cassandra): automated image update 2026-07-30 09:37:19 +00:00
flux-bot
ebd5d71afa chore(cassandra): automated image update 2026-07-30 09:34:20 +00:00
flux-bot
ec91045921 chore(cassandra): automated image update 2026-07-30 09:34:16 +00:00
jenkins
16e10925a9 Deploy Cassandra 0.8.85 2026-07-30 06:01:12 -03:00
flux-bot
21f56239dc chore(cassandra): automated image update 2026-07-30 08:57:23 +00:00
flux-bot
6747ed5e27 chore(cassandra): automated image update 2026-07-30 08:55:20 +00:00
flux-bot
b325c046fa chore(cassandra): automated image update 2026-07-30 08:51:23 +00:00
flux-bot
bbaf9d6ace chore(cassandra): automated image update 2026-07-30 08:51:18 +00:00
jenkins
657dbcb634 Deploy Cassandra 0.8.84 2026-07-30 04:11:38 -03:00
flux-bot
dd3e2fe5d1 chore(cassandra): automated image update 2026-07-30 06:41:05 +00:00
flux-bot
5ee7315585 chore(cassandra): automated image update 2026-07-30 06:40:26 +00:00
flux-bot
6de5ed6b8e chore(cassandra): automated image update 2026-07-30 06:37:01 +00:00
flux-bot
1e7908c35d chore(cassandra): automated image update 2026-07-30 06:36:31 +00:00
jenkins
ea3f71883b Deploy Cassandra 0.8.83 2026-07-30 03:02:50 -03:00
flux-bot
66a9967e60 chore(cassandra): automated image update 2026-07-30 05:52:56 +00:00
flux-bot
4b972c9f7e chore(cassandra): automated image update 2026-07-30 05:50:25 +00:00
flux-bot
eef3dea251 chore(cassandra): automated image update 2026-07-30 05:46:54 +00:00
flux-bot
01201e1493 chore(cassandra): automated image update 2026-07-30 05:46:25 +00:00
flux-bot
7e8352221a chore(maintenance): automated image update 2026-07-30 05:15:18 +00:00
jenkins
f822ad387b Deploy Cassandra 0.8.82 2026-07-30 02:10:45 -03:00
flux-bot
ea61b41f54 chore(cassandra): automated image update 2026-07-30 05:09:41 +00:00
flux-bot
c26d4ceb31 chore(cassandra): automated image update 2026-07-30 05:04:42 +00:00
flux-bot
ee34e84315 chore(cassandra): automated image update 2026-07-30 05:03:43 +00:00
jenkins
4d62a96eaa Deploy Cassandra 0.8.81 2026-07-30 01:50:33 -03:00
flux-bot
dacec009cf chore(cassandra): automated image update 2026-07-30 04:49:18 +00:00
flux-bot
85460b882b chore(cassandra): automated image update 2026-07-30 04:46:18 +00:00
flux-bot
9b44ef43fc chore(cassandra): automated image update 2026-07-30 04:42:16 +00:00
flux-bot
a5be684736 chore(cassandra): automated image update 2026-07-30 04:41:16 +00:00
jenkins
965a95e594 Deploy Cassandra 0.8.80 2026-07-30 01:28:26 -03:00
flux-bot
1448387e2e chore(cassandra): automated image update 2026-07-30 04:26:56 +00:00
flux-bot
7a86cefafd chore(cassandra): automated image update 2026-07-30 04:24:56 +00:00
flux-bot
09361eae97 chore(cassandra): automated image update 2026-07-30 04:21:59 +00:00
flux-bot
47c10c0e21 chore(cassandra): automated image update 2026-07-30 04:21:55 +00:00
jenkins
e04dcbe0f6 Deploy Cassandra 0.8.79 2026-07-30 01:08:40 -03:00
flux-bot
33b755ed08 chore(cassandra): automated image update 2026-07-30 04:06:19 +00:00
flux-bot
06b1ddb2a4 chore(cassandra): automated image update 2026-07-30 04:05:05 +00:00
flux-bot
8135a3d412 chore(cassandra): automated image update 2026-07-30 04:01:08 +00:00
flux-bot
e239d41d2e chore(cassandra): automated image update 2026-07-30 04:01:03 +00:00
jenkins
da6f767f4c Deploy Cassandra 0.8.78 2026-07-30 00:48:27 -03:00
flux-bot
4e51c5a80c chore(cassandra): automated image update 2026-07-30 03:45:25 +00:00
flux-bot
a836449a50 chore(cassandra): automated image update 2026-07-30 03:43:25 +00:00
flux-bot
00df27b910 chore(cassandra): automated image update 2026-07-30 03:38:10 +00:00
flux-bot
f88077943e chore(cassandra): automated image update 2026-07-30 03:37:10 +00:00
jenkins
3f5b5b2446 Deploy Cassandra 0.8.77 2026-07-30 00:09:06 -03:00
flux-bot
57bfb2fa79 chore(cassandra): automated image update 2026-07-30 03:07:25 +00:00
flux-bot
5a5e2ed631 chore(cassandra): automated image update 2026-07-30 03:06:25 +00:00
flux-bot
f04dd44858 chore(cassandra): automated image update 2026-07-30 03:02:56 +00:00
flux-bot
46828bd0ae chore(cassandra): automated image update 2026-07-30 03:01:57 +00:00
jenkins
541dca3cc3 Deploy Cassandra 0.8.76 2026-07-29 23:50:39 -03:00
flux-bot
168c2dd939 chore(cassandra): automated image update 2026-07-30 02:48:30 +00:00
flux-bot
ce996582ba chore(cassandra): automated image update 2026-07-30 02:46:29 +00:00
flux-bot
11e0489bc1 chore(cassandra): automated image update 2026-07-30 02:43:14 +00:00
flux-bot
baa07fa943 chore(cassandra): automated image update 2026-07-30 02:42:25 +00:00
jenkins
3a3606a9e6 Deploy Cassandra 0.8.75 2026-07-29 23:20:14 -03:00
flux-bot
7eecc49423 chore(cassandra): automated image update 2026-07-30 02:18:44 +00:00
flux-bot
28ea846868 chore(cassandra): automated image update 2026-07-30 02:16:41 +00:00
flux-bot
1d18906628 chore(cassandra): automated image update 2026-07-30 02:13:26 +00:00
flux-bot
970e483955 chore(cassandra): automated image update 2026-07-30 02:12:26 +00:00
jenkins
2850865511 Deploy Cassandra 0.8.74 2026-07-29 22:59:47 -03:00
flux-bot
50dda50e5a chore(cassandra): automated image update 2026-07-30 01:56:45 +00:00
flux-bot
1089f2e3a6 chore(cassandra): automated image update 2026-07-30 01:54:17 +00:00
flux-bot
e86edcd5aa chore(cassandra): automated image update 2026-07-30 01:51:16 +00:00
flux-bot
c858d62fa5 chore(cassandra): automated image update 2026-07-30 01:50:16 +00:00
flux-bot
5176196962 chore(maintenance): automated image update 2026-07-30 01:45:55 +00:00
flux-bot
30930e3e39 chore(maintenance): automated image update 2026-07-30 01:41:36 +00:00
flux-bot
c66177fde1 chore(maintenance): automated image update 2026-07-30 01:38:33 +00:00
jenkins
daaba07e50 Deploy Cassandra 0.8.73 2026-07-29 22:30:14 -03:00
flux-bot
73df1dd440 chore(cassandra): automated image update 2026-07-30 01:28:59 +00:00
flux-bot
95dea802a6 chore(cassandra): automated image update 2026-07-30 01:27:32 +00:00
flux-bot
3979f80213 chore(cassandra): automated image update 2026-07-30 01:22:27 +00:00
flux-bot
5d9e44343a chore(cassandra): automated image update 2026-07-30 01:22:03 +00:00
jenkins
aa684fd34a Deploy Cassandra 0.8.72 2026-07-29 21:58:40 -03:00
flux-bot
fb9594f7c7 chore(cassandra): automated image update 2026-07-30 00:55:57 +00:00
flux-bot
335f70866f chore(cassandra): automated image update 2026-07-30 00:54:57 +00:00
flux-bot
07e1b6f17f chore(cassandra): automated image update 2026-07-30 00:51:57 +00:00
flux-bot
710cfee45d chore(cassandra): automated image update 2026-07-30 00:50:57 +00:00
jenkins
7f0b7176e2 chore(cassandra): deploy 0.8.51 2026-07-28 09:32:14 -03:00
jenkins
11927b1512 Deploy Cassandra 0.8.50 2026-07-28 07:57:25 -03:00
jenkins
26c909a09c Deploy cassandra 0.8.49 2026-07-28 06:33:02 -03:00
jenkins
63bf422eb4 Preserve Cassandra legacy BYOK during cutover 2026-07-25 08:16:35 -03:00
jenkins
a4d8e58a13 feat(cassandra): add parallel migration infrastructure 2026-07-25 00:18:48 -03:00
jenkins
2ecd241507 Deploy Cassandra 0.7.65 generator worker 2026-07-24 02:02:27 -03:00
361 changed files with 20758 additions and 1994 deletions

5
.gitignore vendored
View File

@ -5,6 +5,7 @@
__pycache__/
*.py[cod]
.pytest_cache
.ruff_cache/
.coverage
build/
test-results/
@ -12,6 +13,7 @@ artifacts/
.venv
.venv-ci
tmp/
.mainfix/
.terraform/
**/.terraform/
*.tfvars
@ -19,3 +21,6 @@ tmp/
*.tfstate.*
crash.log
terraform/atlas/generated/
# Local demo credentials (never commit)
scripts/ops/hermes_triage_demo.env

55
Jenkinsfile vendored
View File

@ -56,6 +56,11 @@ spec:
command:
- cat
tty: true
- name: semgrep
image: semgrep/semgrep:1.171.0
command:
- cat
tty: true
"""
}
}
@ -70,6 +75,8 @@ spec:
VM_URL = 'http://victoria-metrics-single-server.monitoring.svc.cluster.local:8428'
QUALITY_GATE_SONARQUBE_ENFORCE = '0'
QUALITY_GATE_SONARQUBE_REPORT = 'build/sonarqube-quality-gate.json'
QUALITY_GATE_SEMGREP_ENFORCE = '0'
QUALITY_GATE_SEMGREP_REPORT = 'build/semgrep-report.json'
QUALITY_GATE_IRONBANK_ENFORCE = '1'
QUALITY_GATE_IRONBANK_REQUIRED = '0'
QUALITY_GATE_IRONBANK_REPORT = 'build/ironbank-compliance.json'
@ -110,6 +117,26 @@ spec:
'''
}
}
stage('Collect Semgrep evidence') {
steps {
container('semgrep') {
sh '''#!/bin/sh
set -eu
mkdir -p build
set +e
semgrep scan --config auto --metrics=off --json --output build/semgrep.json .
semgrep_rc=$?
set -e
printf '%s\n' "${semgrep_rc}" > build/semgrep.rc
'''
}
sh '''
set -eu
semgrep_rc="$(cat build/semgrep.rc 2>/dev/null || echo 2)"
python3 ci/scripts/semgrep_report.py --semgrep-json build/semgrep.json --exit-code "${semgrep_rc}" --output "${QUALITY_GATE_SEMGREP_REPORT}" --sonar-issues-output build/semgrep-sonar-issues.json
'''
}
}
stage('Collect SonarQube evidence') {
steps {
container('quality-tools') {
@ -126,6 +153,7 @@ spec:
"-Dsonar.test.inclusions=**/tests/**,**/testing/**,**/*_test.go,**/*.test.ts,**/*.test.tsx,**/*.spec.ts,**/*.spec.tsx"
)
[ -f build/coverage-unit.xml ] && args+=("-Dsonar.python.coverage.reportPaths=build/coverage-unit.xml")
[ -f build/semgrep-sonar-issues.json ] && args+=("-Dsonar.externalIssuesReportPaths=build/semgrep-sonar-issues.json")
set +e
sonar-scanner "${args[@]}" | tee build/sonar-scanner.log
rc=${PIPESTATUS[0]}
@ -326,6 +354,33 @@ PY
esac
fi
if enabled "${QUALITY_GATE_SEMGREP_ENFORCE:-0}"; then
semgrep_status="$(python3 - <<'PY'
import json
from pathlib import Path
path = Path("build/semgrep-report.json")
if not path.exists():
print("missing")
raise SystemExit(0)
try:
payload = json.loads(path.read_text(encoding="utf-8"))
except Exception: # noqa: BLE001
print("error")
raise SystemExit(0)
status = str(payload.get("status") or "").strip().lower()
print(status or "missing")
PY
)"
case "${semgrep_status}" in
ok|pass|passed|success) ;;
*)
echo "semgrep gate failed: ${semgrep_status}" >&2
fail=1
;;
esac
fi
ironbank_required="${QUALITY_GATE_IRONBANK_REQUIRED:-0}"
if [ "${PUBLISH_IMAGES:-false}" = "true" ]; then
ironbank_required=1

105
Makefile Normal file
View File

@ -0,0 +1,105 @@
# Makefile
SHELL := /usr/bin/env bash
KUSTOMIZE ?= kustomize
PYTHON ?= $(if $(wildcard .venv/bin/python),.venv/bin/python,python3)
TERRAFORM ?= terraform
TRIVY ?= trivy
BUILD_DIR ?= build
ATLAS_FLUX_ROOT := clusters/atlas/flux-system
AETHER_FLUX_ROOT := clusters/aether/flux-system
AETHER_TF_DIR := terraform/aether
FLUX_KUSTOMIZATION ?= flux-system
FLUX_NAMESPACE ?= flux-system
FLUX_PATH ?= $(ATLAS_FLUX_ROOT)
TRIVY_CACHE_DIR ?= $(BUILD_DIR)/trivy-cache
TRIVY_DB_FLAGS ?= --skip-db-update
.PHONY: help render render-atlas render-aether render-services render-infrastructure validate dashboards knowledge test flux-diff flux-inventory trivy-scan security-report security aether-fmt aether-init aether-validate aether-plan
help:
@printf '%s\n' \
'Targets:' \
' render Render Atlas, Aether, services, and infrastructure.' \
' render-atlas Render the Atlas Flux root.' \
' render-aether Render the Aether Flux root.' \
' render-services Render every service kustomization.' \
' render-infrastructure Render infrastructure kustomizations.' \
' validate Run render checks plus the local quality gate.' \
' dashboards Regenerate Grafana dashboard artifacts.' \
' knowledge Regenerate Atlas knowledge artifacts and comms mirror.' \
' test Run the local quality gate.' \
' flux-diff Diff a Flux Kustomization; override FLUX_KUSTOMIZATION/FLUX_PATH.' \
' flux-inventory Print Flux Kustomization inventory.' \
' trivy-scan Run the local Trivy filesystem scan.' \
' security-report Build IronBank report from build/trivy-fs.json.' \
' security Run Trivy scan and build the IronBank report.' \
' aether-fmt Check Terraform formatting for Aether.' \
' aether-init Initialize Aether Terraform providers.' \
' aether-validate Validate Aether Terraform after init.' \
' aether-plan Plan Aether Terraform from local variables/env.'
render: render-atlas render-aether render-services render-infrastructure
render-atlas:
$(KUSTOMIZE) build $(ATLAS_FLUX_ROOT) >/tmp/titan-iac-atlas-flux-system.yaml
render-aether:
$(KUSTOMIZE) build $(AETHER_FLUX_ROOT) >/tmp/titan-iac-aether-flux-system.yaml
render-services:
@set -euo pipefail; \
while IFS= read -r k; do \
d="$${k%/kustomization.yaml}"; \
printf 'render %s\n' "$$d"; \
$(KUSTOMIZE) build "$$d" >/tmp/titan-iac-render.yaml; \
done < <(find services -name kustomization.yaml | sort)
render-infrastructure:
@set -euo pipefail; \
while IFS= read -r k; do \
d="$${k%/kustomization.yaml}"; \
printf 'render %s\n' "$$d"; \
$(KUSTOMIZE) build "$$d" >/tmp/titan-iac-render.yaml; \
done < <(find infrastructure -name kustomization.yaml | sort)
validate: render test
dashboards:
$(PYTHON) scripts/render/dashboards_render_atlas.py --build
$(PYTHON) scripts/render/dashboards_render_logs.py --build
$(PYTHON) scripts/render/logging_render_observability.py --build
knowledge:
$(PYTHON) scripts/render/knowledge_render_atlas.py --write --sync-comms
test:
$(PYTHON) -m testing.quality_gate --profile local --build-dir $(BUILD_DIR)
flux-diff:
flux diff kustomization $(FLUX_KUSTOMIZATION) --namespace $(FLUX_NAMESPACE) --path $(FLUX_PATH)
flux-inventory:
$(PYTHON) scripts/render/flux_inventory.py $(ATLAS_FLUX_ROOT)
trivy-scan:
mkdir -p $(BUILD_DIR)
$(TRIVY) fs --cache-dir "$(TRIVY_CACHE_DIR)" $(TRIVY_DB_FLAGS) --skip-files clusters/atlas/flux-system/gotk-components.yaml --timeout 5m --no-progress --format json --output $(BUILD_DIR)/trivy-fs.json --scanners vuln,secret,misconfig --severity HIGH,CRITICAL .
security-report:
@test -s $(BUILD_DIR)/trivy-fs.json || { printf '%s\n' 'missing build/trivy-fs.json; run make trivy-scan first'; exit 1; }
$(PYTHON) ci/scripts/supply_chain_report.py --trivy-json $(BUILD_DIR)/trivy-fs.json --waivers ci/titan-iac-trivy-waivers.json --output $(BUILD_DIR)/ironbank-compliance.json
security: trivy-scan security-report
aether-fmt:
$(TERRAFORM) -chdir=$(AETHER_TF_DIR) fmt -check
aether-init:
$(TERRAFORM) -chdir=$(AETHER_TF_DIR) init
aether-validate:
$(TERRAFORM) -chdir=$(AETHER_TF_DIR) validate
aether-plan:
$(TERRAFORM) -chdir=$(AETHER_TF_DIR) plan

View File

@ -55,6 +55,11 @@ spec:
command:
- cat
tty: true
- name: semgrep
image: semgrep/semgrep:1.171.0
command:
- cat
tty: true
"""
}
}
@ -69,6 +74,8 @@ spec:
VM_URL = 'http://victoria-metrics-single-server.monitoring.svc.cluster.local:8428'
QUALITY_GATE_SONARQUBE_ENFORCE = '0'
QUALITY_GATE_SONARQUBE_REPORT = 'build/sonarqube-quality-gate.json'
QUALITY_GATE_SEMGREP_ENFORCE = '0'
QUALITY_GATE_SEMGREP_REPORT = 'build/semgrep-report.json'
QUALITY_GATE_IRONBANK_ENFORCE = '1'
QUALITY_GATE_IRONBANK_REQUIRED = '0'
QUALITY_GATE_IRONBANK_REPORT = 'build/ironbank-compliance.json'
@ -109,6 +116,26 @@ spec:
'''
}
}
stage('Collect Semgrep evidence') {
steps {
container('semgrep') {
sh '''#!/bin/sh
set -eu
mkdir -p build
set +e
semgrep scan --config auto --metrics=off --json --output build/semgrep.json .
semgrep_rc=$?
set -e
printf '%s\n' "${semgrep_rc}" > build/semgrep.rc
'''
}
sh '''
set -eu
semgrep_rc="$(cat build/semgrep.rc 2>/dev/null || echo 2)"
python3 ci/scripts/semgrep_report.py --semgrep-json build/semgrep.json --exit-code "${semgrep_rc}" --output "${QUALITY_GATE_SEMGREP_REPORT}" --sonar-issues-output build/semgrep-sonar-issues.json
'''
}
}
stage('Collect SonarQube evidence') {
steps {
container('quality-tools') {
@ -125,6 +152,7 @@ spec:
"-Dsonar.test.inclusions=**/tests/**,**/testing/**,**/*_test.go,**/*.test.ts,**/*.test.tsx,**/*.spec.ts,**/*.spec.tsx"
)
[ -f build/coverage-unit.xml ] && args+=("-Dsonar.python.coverage.reportPaths=build/coverage-unit.xml")
[ -f build/semgrep-sonar-issues.json ] && args+=("-Dsonar.externalIssuesReportPaths=build/semgrep-sonar-issues.json")
set +e
sonar-scanner "${args[@]}" | tee build/sonar-scanner.log
rc=${PIPESTATUS[0]}
@ -325,6 +353,33 @@ PY
esac
fi
if enabled "${QUALITY_GATE_SEMGREP_ENFORCE:-0}"; then
semgrep_status="$(python3 - <<'PY'
import json
from pathlib import Path
path = Path("build/semgrep-report.json")
if not path.exists():
print("missing")
raise SystemExit(0)
try:
payload = json.loads(path.read_text(encoding="utf-8"))
except Exception: # noqa: BLE001
print("error")
raise SystemExit(0)
status = str(payload.get("status") or "").strip().lower()
print(status or "missing")
PY
)"
case "${semgrep_status}" in
ok|pass|passed|success) ;;
*)
echo "semgrep gate failed: ${semgrep_status}" >&2
fail=1
;;
esac
fi
ironbank_required="${QUALITY_GATE_IRONBANK_REQUIRED:-0}"
if [ "${PUBLISH_IMAGES:-false}" = "true" ]; then
ironbank_required=1

View File

@ -20,6 +20,7 @@ CANONICAL_CHECKS = _quality_helpers.CANONICAL_CHECKS
_build_check_statuses = _quality_helpers._build_check_statuses
_combine_statuses = _quality_helpers._combine_statuses
_infer_sonarqube_status = _quality_helpers._infer_sonarqube_status
_infer_semgrep_status = _quality_helpers._infer_semgrep_status
_infer_source_lines_over_500 = _quality_helpers._infer_source_lines_over_500
_infer_supply_chain_status = _quality_helpers._infer_supply_chain_status
_infer_workspace_coverage_percent = _quality_helpers._infer_workspace_coverage_percent
@ -286,6 +287,7 @@ def main() -> int:
if source_lines_over_500 <= 0:
source_lines_over_500 = _infer_source_lines_over_500(summary)
sonarqube_report = _load_optional_json(os.getenv("QUALITY_GATE_SONARQUBE_REPORT", "build/sonarqube-quality-gate.json"))
semgrep_report = _load_optional_json(os.getenv("QUALITY_GATE_SEMGREP_REPORT", "build/semgrep-report.json"))
supply_chain_report = _load_optional_json(os.getenv("QUALITY_GATE_IRONBANK_REPORT", "build/ironbank-compliance.json"))
truthy = {"1", "true", "yes", "on"}
supply_chain_required = (
@ -298,6 +300,7 @@ def main() -> int:
workspace_line_coverage_percent=workspace_line_coverage_percent,
source_lines_over_500=source_lines_over_500,
sonarqube_report=sonarqube_report,
semgrep_report=semgrep_report,
supply_chain_report=supply_chain_report,
supply_chain_required=supply_chain_required,
)

View File

@ -18,6 +18,7 @@ CANONICAL_CHECKS = [
"docs_naming",
"gate_glue",
"sonarqube",
"semgrep",
"supply_chain",
]
@ -135,12 +136,26 @@ def _infer_supply_chain_status(report: dict, required: bool) -> str:
return normalized
def _infer_semgrep_status(report: dict) -> str:
"""Infer canonical Semgrep check status from its JSON report payload."""
if not report:
return "not_applicable"
status = report.get("status")
if status is None:
blocking_findings = report.get("blocking_findings")
errors_total = report.get("errors_total")
if isinstance(blocking_findings, int) and isinstance(errors_total, int):
return "failed" if blocking_findings > 0 or errors_total > 0 else "ok"
return _normalize_result_status(str(status) if status is not None else None, default="failed")
def _build_check_statuses(
summary: dict | None,
tests: dict[str, int],
workspace_line_coverage_percent: float,
source_lines_over_500: int,
sonarqube_report: dict,
semgrep_report: dict,
supply_chain_report: dict,
supply_chain_required: bool,
) -> dict[str, str]:
@ -188,6 +203,7 @@ def _build_check_statuses(
gate_glue_status = _combine_statuses(candidates) if candidates else "not_applicable"
sonarqube_status = status_by_name.get("sonarqube") or _infer_sonarqube_status(sonarqube_report)
semgrep_status = status_by_name.get("semgrep") or _infer_semgrep_status(semgrep_report)
supply_chain_status = status_by_name.get("supply_chain") or _infer_supply_chain_status(
supply_chain_report,
required=supply_chain_required,
@ -200,5 +216,6 @@ def _build_check_statuses(
"docs_naming": docs_naming_status,
"gate_glue": gate_glue_status,
"sonarqube": sonarqube_status,
"semgrep": semgrep_status,
"supply_chain": supply_chain_status,
}

View File

@ -0,0 +1,159 @@
#!/usr/bin/env python3
"""Build a compact Semgrep gate report from Semgrep JSON output."""
from __future__ import annotations
import argparse
import json
from pathlib import Path
from typing import Any
BLOCKING_SEVERITIES = {"ERROR"}
SEVERITIES = ("ERROR", "WARNING", "INFO", "UNKNOWN")
SONAR_SEVERITY_BY_SEMGREP = {
"ERROR": "CRITICAL",
"WARNING": "MAJOR",
"INFO": "MINOR",
"UNKNOWN": "INFO",
}
def _read_json(path: Path) -> dict[str, Any]:
"""Read a JSON object from disk, returning an error-shaped payload on failure."""
if not path.exists():
return {"errors": [{"message": f"report missing: {path}"}], "results": []}
try:
payload = json.loads(path.read_text(encoding="utf-8"))
except json.JSONDecodeError as exc:
return {"errors": [{"message": f"invalid JSON: {exc}"}], "results": []}
if not isinstance(payload, dict):
return {"errors": [{"message": "report payload is not an object"}], "results": []}
return payload
def _finding_severity(finding: dict[str, Any]) -> str:
"""Return a normalized Semgrep finding severity."""
extra = finding.get("extra")
severity = extra.get("severity") if isinstance(extra, dict) else None
normalized = str(severity or "UNKNOWN").strip().upper()
return normalized if normalized in SEVERITIES else "UNKNOWN"
def build_report(
semgrep_payload: dict[str, Any],
*,
semgrep_exit_code: int,
blocking_severities: set[str] | None = None,
) -> dict[str, Any]:
"""Summarize Semgrep evidence into the quality-gate report contract."""
blocking = blocking_severities or BLOCKING_SEVERITIES
raw_results = semgrep_payload.get("results", [])
raw_errors = semgrep_payload.get("errors", [])
results = [item for item in raw_results if isinstance(item, dict)] if isinstance(raw_results, list) else []
errors = [item for item in raw_errors if isinstance(item, dict)] if isinstance(raw_errors, list) else []
severity_counts = {severity: 0 for severity in SEVERITIES}
blocking_findings = 0
for finding in results:
severity = _finding_severity(finding)
severity_counts[severity] += 1
if severity in blocking:
blocking_findings += 1
engine_error = semgrep_exit_code not in {0, 1} or bool(errors)
status = "failed" if engine_error or blocking_findings else "ok"
return {
"status": status,
"scanner": "semgrep",
"semgrep_rc": semgrep_exit_code,
"findings_total": len(results),
"blocking_findings": blocking_findings,
"errors_total": len(errors),
"severity_counts": severity_counts,
"blocking_severities": sorted(blocking),
}
def _line_number(value: Any, default: int = 1) -> int:
"""Return a Sonar-compatible one-indexed line number."""
try:
line = int(value)
except (TypeError, ValueError):
return default
return max(line, default)
def _sonar_issue_from_finding(finding: dict[str, Any]) -> dict[str, Any] | None:
"""Convert one Semgrep finding into SonarQube generic issue format."""
path = str(finding.get("path") or "").strip()
if not path:
return None
extra = finding.get("extra") if isinstance(finding.get("extra"), dict) else {}
severity = _finding_severity(finding)
start = finding.get("start") if isinstance(finding.get("start"), dict) else {}
end = finding.get("end") if isinstance(finding.get("end"), dict) else {}
start_line = _line_number(start.get("line") if isinstance(start, dict) else None)
end_line = _line_number(end.get("line") if isinstance(end, dict) else None, start_line)
if end_line < start_line:
end_line = start_line
return {
"engineId": "semgrep",
"ruleId": str(finding.get("check_id") or "semgrep.unknown"),
"type": "VULNERABILITY" if severity == "ERROR" else "CODE_SMELL",
"severity": SONAR_SEVERITY_BY_SEMGREP[severity],
"primaryLocation": {
"message": str(extra.get("message") or finding.get("check_id") or "Semgrep finding"),
"filePath": path,
"textRange": {
"startLine": start_line,
"endLine": end_line,
},
},
}
def build_sonar_issues(semgrep_payload: dict[str, Any]) -> dict[str, list[dict[str, Any]]]:
"""Build SonarQube generic external issues from Semgrep JSON output."""
raw_results = semgrep_payload.get("results", [])
results = [item for item in raw_results if isinstance(item, dict)] if isinstance(raw_results, list) else []
issues = [issue for finding in results if (issue := _sonar_issue_from_finding(finding))]
return {"issues": issues}
def main(argv: list[str] | None = None) -> int:
"""CLI entrypoint used by Jenkins after Semgrep finishes."""
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--semgrep-json", required=True)
parser.add_argument("--exit-code", type=int, default=0)
parser.add_argument("--output", required=True)
parser.add_argument("--sonar-issues-output")
parser.add_argument(
"--blocking-severity",
action="append",
default=[],
help="Severity that should mark the report failed. Defaults to ERROR.",
)
args = parser.parse_args(argv)
blocking = {item.strip().upper() for item in args.blocking_severity if item.strip()}
payload = _read_json(Path(args.semgrep_json))
report = build_report(
payload,
semgrep_exit_code=args.exit_code,
blocking_severities=blocking or BLOCKING_SEVERITIES,
)
output = Path(args.output)
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="utf-8")
if args.sonar_issues_output:
sonar_output = Path(args.sonar_issues_output)
sonar_output.parent.mkdir(parents=True, exist_ok=True)
sonar_output.write_text(
json.dumps(build_sonar_issues(payload), indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)
return 0
if __name__ == "__main__": # pragma: no cover
raise SystemExit(main())

View File

@ -131,7 +131,11 @@ def build_report(
critical = _count_vulnerabilities(trivy_payload, "CRITICAL")
high = _count_vulnerabilities(trivy_payload, "HIGH")
secrets = _count_secrets(trivy_payload)
status = "ok" if critical == 0 and secrets == 0 and not open_misconfigs else "failed"
status = (
"ok"
if critical == 0 and secrets == 0 and not open_misconfigs and expired_waivers == 0
else "failed"
)
return {
"status": status,

View File

@ -1,7 +1,7 @@
{
"version": 1,
"generated_from": "Jenkins titan-iac build 225 Trivy filesystem scan",
"default_expires_at": "2026-05-22",
"default_expires_at": "2026-09-30",
"ticket": "atlas-quality-wave-k8s-hardening",
"default_reason": "Existing Kubernetes manifest hardening baseline accepted only for the first quality-gate rollout; fix or renew explicitly before expiry.",
"misconfigurations": [
@ -15,15 +15,15 @@
"id": "KSV-0009",
"targets": [
"services/mailu/vip-controller.yaml",
"services/maintenance/k3s-agent-restart-daemonset.yaml"
"services/maintenance/node-ops/k3s-agent-restart-daemonset.yaml"
]
},
{
"id": "KSV-0010",
"targets": [
"services/maintenance/k3s-agent-restart-daemonset.yaml",
"services/maintenance/metis-sentinel-amd64-daemonset.yaml",
"services/maintenance/metis-sentinel-arm64-daemonset.yaml",
"services/maintenance/node-ops/k3s-agent-restart-daemonset.yaml",
"services/maintenance/node-ops/metis-sentinel-amd64-daemonset.yaml",
"services/maintenance/node-ops/metis-sentinel-arm64-daemonset.yaml",
"services/monitoring/jetson-tegrastats-exporter.yaml"
]
},
@ -47,8 +47,8 @@
"services/bstein-dev-home/backend-deployment.yaml",
"services/bstein-dev-home/chat-ai-gateway-deployment.yaml",
"services/bstein-dev-home/frontend-deployment.yaml",
"services/bstein-dev-home/oneoffs/migrations/portal-migrate-job.yaml",
"services/bstein-dev-home/oneoffs/portal-onboarding-e2e-test-job.yaml",
"services/bstein-dev-home/migration-jobs/portal-migrate-job.yaml",
"services/bstein-dev-home/validation-jobs/portal-onboarding-e2e-test-job.yaml",
"services/bstein-dev-home/vault-sync-deployment.yaml",
"services/bstein-dev-home/vaultwarden-cred-sync-cronjob.yaml",
"services/comms/atlasbot-deployment.yaml",
@ -59,16 +59,16 @@
"services/comms/livekit-token-deployment.yaml",
"services/comms/livekit.yaml",
"services/comms/mas-deployment.yaml",
"services/comms/oneoffs/bstein-force-leave-job.yaml",
"services/comms/oneoffs/comms-secrets-ensure-job.yaml",
"services/comms/oneoffs/mas-admin-client-secret-ensure-job.yaml",
"services/comms/oneoffs/mas-db-ensure-job.yaml",
"services/comms/oneoffs/mas-local-users-ensure-job.yaml",
"services/comms/oneoffs/othrys-kick-numeric-job.yaml",
"services/comms/oneoffs/synapse-admin-ensure-job.yaml",
"services/comms/oneoffs/synapse-seeder-admin-ensure-job.yaml",
"services/comms/oneoffs/synapse-signingkey-ensure-job.yaml",
"services/comms/oneoffs/synapse-user-seed-job.yaml",
"services/comms/repair-jobs/bstein-force-leave-job.yaml",
"services/comms/bootstrap-jobs/comms-secrets-ensure-job.yaml",
"services/comms/bootstrap-jobs/mas-admin-client-secret-ensure-job.yaml",
"services/comms/bootstrap-jobs/mas-db-ensure-job.yaml",
"services/comms/bootstrap-jobs/mas-local-users-ensure-job.yaml",
"services/comms/repair-jobs/othrys-kick-numeric-job.yaml",
"services/comms/bootstrap-jobs/synapse-admin-ensure-job.yaml",
"services/comms/bootstrap-jobs/synapse-seeder-admin-ensure-job.yaml",
"services/comms/bootstrap-jobs/synapse-signingkey-ensure-job.yaml",
"services/comms/bootstrap-jobs/synapse-user-seed-job.yaml",
"services/comms/pin-othrys-job.yaml",
"services/comms/reset-othrys-room-job.yaml",
"services/comms/seed-othrys-room.yaml",
@ -83,7 +83,7 @@
"services/finance/firefly-cronjob.yaml",
"services/finance/firefly-deployment.yaml",
"services/finance/firefly-user-sync-cronjob.yaml",
"services/finance/oneoffs/finance-secrets-ensure-job.yaml",
"services/finance/bootstrap-jobs/finance-secrets-ensure-job.yaml",
"services/gitea/deployment.yaml",
"services/harbor/vault-sync-deployment.yaml",
"services/health/wger-admin-ensure-cronjob.yaml",
@ -94,63 +94,62 @@
"services/jenkins/deployment.yaml",
"services/jenkins/vault-sync-deployment.yaml",
"services/keycloak/deployment.yaml",
"services/keycloak/oneoffs/actual-oidc-secret-ensure-job.yaml",
"services/keycloak/oneoffs/harbor-oidc-secret-ensure-job.yaml",
"services/keycloak/oneoffs/ldap-federation-job.yaml",
"services/keycloak/oneoffs/logs-oidc-secret-ensure-job.yaml",
"services/keycloak/oneoffs/mas-secrets-ensure-job.yaml",
"services/keycloak/oneoffs/metis-node-passwords-secret-ensure-job.yaml",
"services/keycloak/oneoffs/metis-oidc-secret-ensure-job.yaml",
"services/keycloak/oneoffs/metis-ssh-keys-secret-ensure-job.yaml",
"services/keycloak/oneoffs/portal-admin-client-secret-ensure-job.yaml",
"services/keycloak/oneoffs/portal-e2e-client-job.yaml",
"services/keycloak/oneoffs/portal-e2e-execute-actions-email-test-job.yaml",
"services/keycloak/oneoffs/portal-e2e-target-client-job.yaml",
"services/keycloak/oneoffs/portal-e2e-token-exchange-permissions-job.yaml",
"services/keycloak/oneoffs/portal-e2e-token-exchange-test-job.yaml",
"services/keycloak/oneoffs/quality-oidc-secret-ensure-job.yaml",
"services/keycloak/oneoffs/realm-settings-job.yaml",
"services/keycloak/oneoffs/soteria-oidc-secret-ensure-job.yaml",
"services/keycloak/oneoffs/synapse-oidc-secret-ensure-job.yaml",
"services/keycloak/oneoffs/user-overrides-job.yaml",
"services/keycloak/oneoffs/vault-oidc-secret-ensure-job.yaml",
"services/keycloak/bootstrap-jobs/actual-oidc-secret-ensure-job.yaml",
"services/keycloak/bootstrap-jobs/harbor-oidc-secret-ensure-job.yaml",
"services/keycloak/bootstrap-jobs/ldap-federation-job.yaml",
"services/keycloak/bootstrap-jobs/logs-oidc-secret-ensure-job.yaml",
"services/keycloak/bootstrap-jobs/mas-secrets-ensure-job.yaml",
"services/keycloak/bootstrap-jobs/metis-node-passwords-secret-ensure-job.yaml",
"services/keycloak/bootstrap-jobs/metis-oidc-secret-ensure-job.yaml",
"services/keycloak/bootstrap-jobs/metis-ssh-keys-secret-ensure-job.yaml",
"services/keycloak/bootstrap-jobs/portal-admin-client-secret-ensure-job.yaml",
"services/keycloak/validation-jobs/portal-e2e-client-job.yaml",
"services/keycloak/validation-jobs/portal-e2e-execute-actions-email-test-job.yaml",
"services/keycloak/validation-jobs/portal-e2e-target-client-job.yaml",
"services/keycloak/validation-jobs/portal-e2e-token-exchange-permissions-job.yaml",
"services/keycloak/validation-jobs/portal-e2e-token-exchange-test-job.yaml",
"services/keycloak/bootstrap-jobs/quality-oidc-secret-ensure-job.yaml",
"services/keycloak/bootstrap-jobs/realm-settings-job.yaml",
"services/keycloak/bootstrap-jobs/soteria-oidc-secret-ensure-job.yaml",
"services/keycloak/bootstrap-jobs/synapse-oidc-secret-ensure-job.yaml",
"services/keycloak/bootstrap-jobs/user-overrides-job.yaml",
"services/keycloak/bootstrap-jobs/vault-oidc-secret-ensure-job.yaml",
"services/keycloak/vault-sync-deployment.yaml",
"services/logging/node-image-gc-rpi4-daemonset.yaml",
"services/logging/node-image-prune-rpi5-daemonset.yaml",
"services/logging/node-log-rotation-daemonset.yaml",
"services/logging/oauth2-proxy.yaml",
"services/logging/oneoffs/opensearch-dashboards-setup-job.yaml",
"services/logging/oneoffs/opensearch-ism-job.yaml",
"services/logging/oneoffs/opensearch-observability-setup-job.yaml",
"services/logging/bootstrap-jobs/opensearch-dashboards-setup-job.yaml",
"services/logging/bootstrap-jobs/opensearch-ism-job.yaml",
"services/logging/bootstrap-jobs/opensearch-observability-setup-job.yaml",
"services/logging/opensearch-prune-cronjob.yaml",
"services/logging/vault-sync-deployment.yaml",
"services/mailu/mailu-sync-cronjob.yaml",
"services/mailu/mailu-sync-listener.yaml",
"services/mailu/oneoffs/mailu-sync-job.yaml",
"services/mailu/vault-sync-deployment.yaml",
"services/mailu/vip-controller.yaml",
"services/maintenance/ariadne-deployment.yaml",
"services/maintenance/disable-k3s-traefik-daemonset.yaml",
"services/maintenance/image-sweeper-cronjob.yaml",
"services/maintenance/k3s-agent-restart-daemonset.yaml",
"services/maintenance/metis-deployment.yaml",
"services/maintenance/metis-k3s-token-sync-cronjob.yaml",
"services/maintenance/metis-sentinel-amd64-daemonset.yaml",
"services/maintenance/metis-sentinel-arm64-daemonset.yaml",
"services/maintenance/node-image-sweeper-daemonset.yaml",
"services/maintenance/node-nofile-daemonset.yaml",
"services/maintenance/oauth2-proxy-metis.yaml",
"services/maintenance/oauth2-proxy-soteria.yaml",
"services/maintenance/oneoffs/ariadne-migrate-job.yaml",
"services/maintenance/oneoffs/k3s-traefik-cleanup-job.yaml",
"services/maintenance/oneoffs/titan-24-rootfs-sweep-job.yaml",
"services/maintenance/pod-cleaner-cronjob.yaml",
"services/maintenance/soteria-deployment.yaml",
"services/maintenance/vault-sync-deployment.yaml",
"services/maintenance/apps/ariadne-deployment.yaml",
"services/maintenance/node-ops/disable-k3s-traefik-daemonset.yaml",
"services/maintenance/node-ops/image-sweeper-cronjob.yaml",
"services/maintenance/node-ops/k3s-agent-restart-daemonset.yaml",
"services/maintenance/apps/metis-deployment.yaml",
"services/maintenance/apps/metis-k3s-token-sync-cronjob.yaml",
"services/maintenance/node-ops/metis-sentinel-amd64-daemonset.yaml",
"services/maintenance/node-ops/metis-sentinel-arm64-daemonset.yaml",
"services/maintenance/node-ops/node-image-sweeper-daemonset.yaml",
"services/maintenance/node-ops/node-nofile-daemonset.yaml",
"services/maintenance/networking/oauth2-proxy-metis.yaml",
"services/maintenance/networking/oauth2-proxy-soteria.yaml",
"services/maintenance/migration-jobs/ariadne-migrate-job.yaml",
"services/maintenance/repair-jobs/k3s-traefik-cleanup-job.yaml",
"services/maintenance/repair-jobs/titan-24-rootfs-sweep-job.yaml",
"services/maintenance/node-ops/pod-cleaner-cronjob.yaml",
"services/maintenance/apps/soteria-deployment.yaml",
"services/maintenance/bootstrap/vault-sync-deployment.yaml",
"services/monitoring/dcgm-exporter.yaml",
"services/monitoring/jetson-tegrastats-exporter.yaml",
"services/monitoring/oneoffs/grafana-org-bootstrap.yaml",
"services/monitoring/oneoffs/grafana-user-dedupe-job.yaml",
"services/monitoring/bootstrap-jobs/grafana-org-bootstrap.yaml",
"services/monitoring/repair-jobs/grafana-user-dedupe-job.yaml",
"services/monitoring/platform-quality-gateway-deployment.yaml",
"services/monitoring/platform-quality-suite-probe-cronjob.yaml",
"services/monitoring/postmark-exporter-deployment.yaml",
@ -188,15 +187,15 @@
"services/logging/node-image-gc-rpi4-daemonset.yaml",
"services/logging/node-image-prune-rpi5-daemonset.yaml",
"services/logging/node-log-rotation-daemonset.yaml",
"services/maintenance/disable-k3s-traefik-daemonset.yaml",
"services/maintenance/image-sweeper-cronjob.yaml",
"services/maintenance/k3s-agent-restart-daemonset.yaml",
"services/maintenance/metis-deployment.yaml",
"services/maintenance/metis-sentinel-amd64-daemonset.yaml",
"services/maintenance/metis-sentinel-arm64-daemonset.yaml",
"services/maintenance/node-image-sweeper-daemonset.yaml",
"services/maintenance/node-nofile-daemonset.yaml",
"services/maintenance/oneoffs/titan-24-rootfs-sweep-job.yaml",
"services/maintenance/node-ops/disable-k3s-traefik-daemonset.yaml",
"services/maintenance/node-ops/image-sweeper-cronjob.yaml",
"services/maintenance/node-ops/k3s-agent-restart-daemonset.yaml",
"services/maintenance/apps/metis-deployment.yaml",
"services/maintenance/node-ops/metis-sentinel-amd64-daemonset.yaml",
"services/maintenance/node-ops/metis-sentinel-arm64-daemonset.yaml",
"services/maintenance/node-ops/node-image-sweeper-daemonset.yaml",
"services/maintenance/node-ops/node-nofile-daemonset.yaml",
"services/maintenance/repair-jobs/titan-24-rootfs-sweep-job.yaml",
"services/monitoring/dcgm-exporter.yaml",
"services/monitoring/jetson-tegrastats-exporter.yaml"
]
@ -211,7 +210,7 @@
"services/comms/comms-secrets-ensure-rbac.yaml",
"services/comms/mas-db-ensure-rbac.yaml",
"services/comms/mas-secrets-ensure-rbac.yaml",
"services/maintenance/soteria-rbac.yaml"
"services/maintenance/apps/soteria-rbac.yaml"
]
},
{
@ -226,7 +225,7 @@
"services/comms/comms-secrets-ensure-rbac.yaml",
"services/comms/mas-db-ensure-rbac.yaml",
"services/jenkins/serviceaccount.yaml",
"services/maintenance/ariadne-rbac.yaml"
"services/maintenance/apps/ariadne-rbac.yaml"
]
},
{
@ -235,8 +234,8 @@
"infrastructure/cert-manager/cleanup/cert-manager-cleanup-rbac.yaml",
"infrastructure/longhorn/adopt/longhorn-adopt-rbac.yaml",
"services/jenkins/serviceaccount.yaml",
"services/maintenance/disable-k3s-traefik-rbac.yaml",
"services/maintenance/k3s-traefik-cleanup-rbac.yaml"
"services/maintenance/node-ops/disable-k3s-traefik-rbac.yaml",
"services/maintenance/node-ops/k3s-traefik-cleanup-rbac.yaml"
]
},
{
@ -266,8 +265,8 @@
"services/bstein-dev-home/backend-deployment.yaml",
"services/bstein-dev-home/chat-ai-gateway-deployment.yaml",
"services/bstein-dev-home/frontend-deployment.yaml",
"services/bstein-dev-home/oneoffs/migrations/portal-migrate-job.yaml",
"services/bstein-dev-home/oneoffs/portal-onboarding-e2e-test-job.yaml",
"services/bstein-dev-home/migration-jobs/portal-migrate-job.yaml",
"services/bstein-dev-home/validation-jobs/portal-onboarding-e2e-test-job.yaml",
"services/bstein-dev-home/vault-sync-deployment.yaml",
"services/bstein-dev-home/vaultwarden-cred-sync-cronjob.yaml",
"services/comms/atlasbot-deployment.yaml",
@ -277,16 +276,16 @@
"services/comms/livekit-token-deployment.yaml",
"services/comms/livekit.yaml",
"services/comms/mas-deployment.yaml",
"services/comms/oneoffs/bstein-force-leave-job.yaml",
"services/comms/oneoffs/comms-secrets-ensure-job.yaml",
"services/comms/oneoffs/mas-admin-client-secret-ensure-job.yaml",
"services/comms/oneoffs/mas-db-ensure-job.yaml",
"services/comms/oneoffs/mas-local-users-ensure-job.yaml",
"services/comms/oneoffs/othrys-kick-numeric-job.yaml",
"services/comms/oneoffs/synapse-admin-ensure-job.yaml",
"services/comms/oneoffs/synapse-seeder-admin-ensure-job.yaml",
"services/comms/oneoffs/synapse-signingkey-ensure-job.yaml",
"services/comms/oneoffs/synapse-user-seed-job.yaml",
"services/comms/repair-jobs/bstein-force-leave-job.yaml",
"services/comms/bootstrap-jobs/comms-secrets-ensure-job.yaml",
"services/comms/bootstrap-jobs/mas-admin-client-secret-ensure-job.yaml",
"services/comms/bootstrap-jobs/mas-db-ensure-job.yaml",
"services/comms/bootstrap-jobs/mas-local-users-ensure-job.yaml",
"services/comms/repair-jobs/othrys-kick-numeric-job.yaml",
"services/comms/bootstrap-jobs/synapse-admin-ensure-job.yaml",
"services/comms/bootstrap-jobs/synapse-seeder-admin-ensure-job.yaml",
"services/comms/bootstrap-jobs/synapse-signingkey-ensure-job.yaml",
"services/comms/bootstrap-jobs/synapse-user-seed-job.yaml",
"services/comms/pin-othrys-job.yaml",
"services/comms/reset-othrys-room-job.yaml",
"services/comms/seed-othrys-room.yaml",
@ -300,7 +299,7 @@
"services/finance/firefly-cronjob.yaml",
"services/finance/firefly-deployment.yaml",
"services/finance/firefly-user-sync-cronjob.yaml",
"services/finance/oneoffs/finance-secrets-ensure-job.yaml",
"services/finance/bootstrap-jobs/finance-secrets-ensure-job.yaml",
"services/gitea/deployment.yaml",
"services/harbor/vault-sync-deployment.yaml",
"services/health/wger-admin-ensure-cronjob.yaml",
@ -309,63 +308,62 @@
"services/jellyfin/loader.yaml",
"services/jenkins/deployment.yaml",
"services/jenkins/vault-sync-deployment.yaml",
"services/keycloak/oneoffs/actual-oidc-secret-ensure-job.yaml",
"services/keycloak/oneoffs/harbor-oidc-secret-ensure-job.yaml",
"services/keycloak/oneoffs/ldap-federation-job.yaml",
"services/keycloak/oneoffs/logs-oidc-secret-ensure-job.yaml",
"services/keycloak/oneoffs/mas-secrets-ensure-job.yaml",
"services/keycloak/oneoffs/metis-node-passwords-secret-ensure-job.yaml",
"services/keycloak/oneoffs/metis-oidc-secret-ensure-job.yaml",
"services/keycloak/oneoffs/metis-ssh-keys-secret-ensure-job.yaml",
"services/keycloak/oneoffs/portal-admin-client-secret-ensure-job.yaml",
"services/keycloak/oneoffs/portal-e2e-client-job.yaml",
"services/keycloak/oneoffs/portal-e2e-execute-actions-email-test-job.yaml",
"services/keycloak/oneoffs/portal-e2e-target-client-job.yaml",
"services/keycloak/oneoffs/portal-e2e-token-exchange-permissions-job.yaml",
"services/keycloak/oneoffs/portal-e2e-token-exchange-test-job.yaml",
"services/keycloak/oneoffs/quality-oidc-secret-ensure-job.yaml",
"services/keycloak/oneoffs/realm-settings-job.yaml",
"services/keycloak/oneoffs/soteria-oidc-secret-ensure-job.yaml",
"services/keycloak/oneoffs/synapse-oidc-secret-ensure-job.yaml",
"services/keycloak/oneoffs/user-overrides-job.yaml",
"services/keycloak/oneoffs/vault-oidc-secret-ensure-job.yaml",
"services/keycloak/bootstrap-jobs/actual-oidc-secret-ensure-job.yaml",
"services/keycloak/bootstrap-jobs/harbor-oidc-secret-ensure-job.yaml",
"services/keycloak/bootstrap-jobs/ldap-federation-job.yaml",
"services/keycloak/bootstrap-jobs/logs-oidc-secret-ensure-job.yaml",
"services/keycloak/bootstrap-jobs/mas-secrets-ensure-job.yaml",
"services/keycloak/bootstrap-jobs/metis-node-passwords-secret-ensure-job.yaml",
"services/keycloak/bootstrap-jobs/metis-oidc-secret-ensure-job.yaml",
"services/keycloak/bootstrap-jobs/metis-ssh-keys-secret-ensure-job.yaml",
"services/keycloak/bootstrap-jobs/portal-admin-client-secret-ensure-job.yaml",
"services/keycloak/validation-jobs/portal-e2e-client-job.yaml",
"services/keycloak/validation-jobs/portal-e2e-execute-actions-email-test-job.yaml",
"services/keycloak/validation-jobs/portal-e2e-target-client-job.yaml",
"services/keycloak/validation-jobs/portal-e2e-token-exchange-permissions-job.yaml",
"services/keycloak/validation-jobs/portal-e2e-token-exchange-test-job.yaml",
"services/keycloak/bootstrap-jobs/quality-oidc-secret-ensure-job.yaml",
"services/keycloak/bootstrap-jobs/realm-settings-job.yaml",
"services/keycloak/bootstrap-jobs/soteria-oidc-secret-ensure-job.yaml",
"services/keycloak/bootstrap-jobs/synapse-oidc-secret-ensure-job.yaml",
"services/keycloak/bootstrap-jobs/user-overrides-job.yaml",
"services/keycloak/bootstrap-jobs/vault-oidc-secret-ensure-job.yaml",
"services/keycloak/vault-sync-deployment.yaml",
"services/logging/node-image-gc-rpi4-daemonset.yaml",
"services/logging/node-image-prune-rpi5-daemonset.yaml",
"services/logging/node-log-rotation-daemonset.yaml",
"services/logging/oauth2-proxy.yaml",
"services/logging/oneoffs/opensearch-dashboards-setup-job.yaml",
"services/logging/oneoffs/opensearch-ism-job.yaml",
"services/logging/oneoffs/opensearch-observability-setup-job.yaml",
"services/logging/bootstrap-jobs/opensearch-dashboards-setup-job.yaml",
"services/logging/bootstrap-jobs/opensearch-ism-job.yaml",
"services/logging/bootstrap-jobs/opensearch-observability-setup-job.yaml",
"services/logging/opensearch-prune-cronjob.yaml",
"services/logging/vault-sync-deployment.yaml",
"services/mailu/mailu-sync-cronjob.yaml",
"services/mailu/mailu-sync-listener.yaml",
"services/mailu/oneoffs/mailu-sync-job.yaml",
"services/mailu/vault-sync-deployment.yaml",
"services/mailu/vip-controller.yaml",
"services/maintenance/ariadne-deployment.yaml",
"services/maintenance/disable-k3s-traefik-daemonset.yaml",
"services/maintenance/image-sweeper-cronjob.yaml",
"services/maintenance/k3s-agent-restart-daemonset.yaml",
"services/maintenance/metis-deployment.yaml",
"services/maintenance/metis-k3s-token-sync-cronjob.yaml",
"services/maintenance/metis-sentinel-amd64-daemonset.yaml",
"services/maintenance/metis-sentinel-arm64-daemonset.yaml",
"services/maintenance/node-image-sweeper-daemonset.yaml",
"services/maintenance/node-nofile-daemonset.yaml",
"services/maintenance/oauth2-proxy-metis.yaml",
"services/maintenance/oauth2-proxy-soteria.yaml",
"services/maintenance/oneoffs/ariadne-migrate-job.yaml",
"services/maintenance/oneoffs/k3s-traefik-cleanup-job.yaml",
"services/maintenance/oneoffs/titan-24-rootfs-sweep-job.yaml",
"services/maintenance/pod-cleaner-cronjob.yaml",
"services/maintenance/soteria-deployment.yaml",
"services/maintenance/vault-sync-deployment.yaml",
"services/maintenance/apps/ariadne-deployment.yaml",
"services/maintenance/node-ops/disable-k3s-traefik-daemonset.yaml",
"services/maintenance/node-ops/image-sweeper-cronjob.yaml",
"services/maintenance/node-ops/k3s-agent-restart-daemonset.yaml",
"services/maintenance/apps/metis-deployment.yaml",
"services/maintenance/apps/metis-k3s-token-sync-cronjob.yaml",
"services/maintenance/node-ops/metis-sentinel-amd64-daemonset.yaml",
"services/maintenance/node-ops/metis-sentinel-arm64-daemonset.yaml",
"services/maintenance/node-ops/node-image-sweeper-daemonset.yaml",
"services/maintenance/node-ops/node-nofile-daemonset.yaml",
"services/maintenance/networking/oauth2-proxy-metis.yaml",
"services/maintenance/networking/oauth2-proxy-soteria.yaml",
"services/maintenance/migration-jobs/ariadne-migrate-job.yaml",
"services/maintenance/repair-jobs/k3s-traefik-cleanup-job.yaml",
"services/maintenance/repair-jobs/titan-24-rootfs-sweep-job.yaml",
"services/maintenance/node-ops/pod-cleaner-cronjob.yaml",
"services/maintenance/apps/soteria-deployment.yaml",
"services/maintenance/bootstrap/vault-sync-deployment.yaml",
"services/monitoring/dcgm-exporter.yaml",
"services/monitoring/jetson-tegrastats-exporter.yaml",
"services/monitoring/oneoffs/grafana-org-bootstrap.yaml",
"services/monitoring/oneoffs/grafana-user-dedupe-job.yaml",
"services/monitoring/bootstrap-jobs/grafana-org-bootstrap.yaml",
"services/monitoring/repair-jobs/grafana-user-dedupe-job.yaml",
"services/monitoring/platform-quality-gateway-deployment.yaml",
"services/monitoring/platform-quality-suite-probe-cronjob.yaml",
"services/monitoring/postmark-exporter-deployment.yaml",
@ -395,12 +393,12 @@
"services/logging/node-image-gc-rpi4-daemonset.yaml",
"services/logging/node-image-prune-rpi5-daemonset.yaml",
"services/logging/node-log-rotation-daemonset.yaml",
"services/maintenance/disable-k3s-traefik-daemonset.yaml",
"services/maintenance/image-sweeper-cronjob.yaml",
"services/maintenance/metis-deployment.yaml",
"services/maintenance/node-image-sweeper-daemonset.yaml",
"services/maintenance/node-nofile-daemonset.yaml",
"services/maintenance/oneoffs/titan-24-rootfs-sweep-job.yaml"
"services/maintenance/node-ops/disable-k3s-traefik-daemonset.yaml",
"services/maintenance/node-ops/image-sweeper-cronjob.yaml",
"services/maintenance/apps/metis-deployment.yaml",
"services/maintenance/node-ops/node-image-sweeper-daemonset.yaml",
"services/maintenance/node-ops/node-nofile-daemonset.yaml",
"services/maintenance/repair-jobs/titan-24-rootfs-sweep-job.yaml"
]
}
]

View File

@ -1,4 +1,4 @@
# infrastructure/modules/profiles/oceanus-validator/kustomization.yaml
# clusters/aether/flux-system/applications/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources: []

View File

@ -0,0 +1,6 @@
# clusters/aether/flux-system/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- platform
- applications

View File

@ -1,4 +1,4 @@
# clusters/oceanus/applications/kustomization.yaml
# clusters/aether/flux-system/platform/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources: []

View File

@ -6,9 +6,10 @@ metadata:
namespace: flux-system
annotations:
kustomize.toolkit.fluxcd.io/ssa: IfNotPresent
atlas.bstein.dev/suspend-reason: "Migration jobs run only during portal schema changes."
spec:
interval: 10m
path: ./services/bstein-dev-home/oneoffs/migrations
path: ./services/bstein-dev-home/migration-jobs
prune: true
force: true
sourceRef:

View File

@ -6,6 +6,7 @@ metadata:
namespace: flux-system
annotations:
kustomize.toolkit.fluxcd.io/ssa: IfNotPresent
atlas.bstein.dev/suspend-reason: "Finance apps are parked until the next storage and SSO pass."
spec:
interval: 10m
suspend: true

View File

@ -6,6 +6,7 @@ metadata:
namespace: flux-system
annotations:
kustomize.toolkit.fluxcd.io/ssa: IfNotPresent
atlas.bstein.dev/suspend-reason: "Game streaming is optional and resumes only for planned use."
spec:
interval: 10m
suspend: true

View File

@ -6,6 +6,7 @@ metadata:
namespace: flux-system
annotations:
kustomize.toolkit.fluxcd.io/ssa: IfNotPresent
atlas.bstein.dev/suspend-reason: "Health stack is staged pending account and mail flows."
spec:
interval: 10m
suspend: true

View File

@ -0,0 +1,28 @@
# clusters/atlas/flux-system/applications/hermes-chat/kustomization.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: hermes-chat
namespace: flux-system
spec:
interval: 10m
path: ./services/hermes-chat
targetNamespace: hermes-chat
prune: true
sourceRef:
kind: GitRepository
name: flux-system
namespace: flux-system
wait: true
timeout: 30m
healthChecks:
- apiVersion: apps/v1
kind: Deployment
name: hermes-chat
namespace: hermes-chat
dependsOn:
- name: cert-manager
- name: core
- name: hermes
- name: keycloak
- name: longhorn

View File

@ -0,0 +1,16 @@
# clusters/atlas/flux-system/applications/hermes-triage-demo/kustomization.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: hermes-triage-demo
namespace: flux-system
spec:
interval: 10m
path: ./services/hermes-triage-demo
prune: true
sourceRef:
kind: GitRepository
name: flux-system
namespace: flux-system
wait: false
timeout: 5m

View File

@ -18,6 +18,10 @@ spec:
wait: true
timeout: 45m
healthChecks:
- apiVersion: apps/v1
kind: Deployment
name: hermes-model-gate
namespace: hermes
- apiVersion: apps/v1
kind: Deployment
name: hermes-ollama
@ -26,6 +30,10 @@ spec:
kind: Deployment
name: hermes
namespace: hermes
- apiVersion: apps/v1
kind: Deployment
name: oauth2-proxy-hermes
namespace: hermes
dependsOn:
- name: cert-manager
- name: core

View File

@ -6,6 +6,7 @@ metadata:
namespace: flux-system
annotations:
kustomize.toolkit.fluxcd.io/ssa: IfNotPresent
atlas.bstein.dev/suspend-reason: "Media stack stays paused while auth and storage changes are staged."
spec:
interval: 10m
suspend: true

View File

@ -6,6 +6,7 @@ metadata:
namespace: flux-system
annotations:
kustomize.toolkit.fluxcd.io/ssa: IfNotPresent
atlas.bstein.dev/suspend-reason: "CI controller changes are applied only during planned maintenance."
spec:
interval: 10m
suspend: true

View File

@ -28,6 +28,8 @@ resources:
- ai-llm/kustomization.yaml
- openclaw/kustomization.yaml
- hermes/kustomization.yaml
- hermes-chat/kustomization.yaml
- hermes-triage-demo/kustomization.yaml
- game-stream/kustomization.yaml
- cassandra-auth/kustomization.yaml
- cassandra/kustomization.yaml

View File

@ -6,6 +6,7 @@ metadata:
namespace: flux-system
annotations:
kustomize.toolkit.fluxcd.io/ssa: IfNotPresent
atlas.bstein.dev/suspend-reason: "Mail stack is staged and resumes only during mail rollout work."
spec:
interval: 10m
suspend: true

View File

@ -6,6 +6,7 @@ metadata:
namespace: flux-system
annotations:
kustomize.toolkit.fluxcd.io/ssa: IfNotPresent
atlas.bstein.dev/suspend-reason: "Mail sync resumes after Nextcloud and Mailu are active."
spec:
interval: 10m
suspend: true

View File

@ -6,6 +6,7 @@ metadata:
namespace: flux-system
annotations:
kustomize.toolkit.fluxcd.io/ssa: IfNotPresent
atlas.bstein.dev/suspend-reason: "Nextcloud is staged pending storage, SSO, and mail validation."
spec:
interval: 10m
suspend: true

View File

@ -6,6 +6,7 @@ metadata:
namespace: flux-system
annotations:
kustomize.toolkit.fluxcd.io/ssa: IfNotPresent
atlas.bstein.dev/suspend-reason: "Outline is staged until shared auth and mail are ready."
spec:
interval: 10m
suspend: true

View File

@ -6,6 +6,7 @@ metadata:
namespace: flux-system
annotations:
kustomize.toolkit.fluxcd.io/ssa: IfNotPresent
atlas.bstein.dev/suspend-reason: "Planka is staged until shared auth and mail are ready."
spec:
interval: 10m
suspend: true

View File

@ -6,6 +6,7 @@ metadata:
namespace: flux-system
annotations:
kustomize.toolkit.fluxcd.io/ssa: IfNotPresent
atlas.bstein.dev/suspend-reason: "Quality stack changes resume only during quality-gate rollout work."
spec:
interval: 10m
suspend: true

View File

@ -6,6 +6,7 @@ metadata:
namespace: flux-system
annotations:
kustomize.toolkit.fluxcd.io/ssa: IfNotPresent
atlas.bstein.dev/suspend-reason: "Climate automation is staged until sensor and control loops are verified."
spec:
interval: 10m
suspend: true

View File

@ -6,6 +6,7 @@ metadata:
namespace: flux-system
annotations:
kustomize.toolkit.fluxcd.io/ssa: IfNotPresent
atlas.bstein.dev/suspend-reason: "Vaultwarden stays separate from SSO rollout and resumes by hand."
spec:
interval: 10m
suspend: true

View File

@ -6,6 +6,7 @@ metadata:
namespace: flux-system
annotations:
kustomize.toolkit.fluxcd.io/ssa: IfNotPresent
atlas.bstein.dev/suspend-reason: "Temporary wallet RPC stack is opt-in for maintenance windows."
spec:
interval: 10m
suspend: true

View File

@ -6,6 +6,7 @@ metadata:
namespace: flux-system
annotations:
kustomize.toolkit.fluxcd.io/ssa: IfNotPresent
atlas.bstein.dev/suspend-reason: "Mining workloads are disabled unless explicitly enabled for a short run."
spec:
interval: 10m
suspend: true

View File

@ -6,6 +6,7 @@ metadata:
namespace: flux-system
annotations:
kustomize.toolkit.fluxcd.io/ssa: IfNotPresent
atlas.bstein.dev/suspend-reason: "Descheduler is paused during node recovery and placement stabilization."
spec:
interval: 30m
suspend: true

View File

@ -6,6 +6,7 @@ metadata:
namespace: flux-system
annotations:
kustomize.toolkit.fluxcd.io/ssa: IfNotPresent
atlas.bstein.dev/suspend-reason: "GitOps UI is optional and resumes only for operator access work."
spec:
interval: 10m
suspend: true

View File

@ -6,6 +6,7 @@ metadata:
namespace: flux-system
annotations:
kustomize.toolkit.fluxcd.io/ssa: IfNotPresent
atlas.bstein.dev/suspend-reason: "Longhorn UI ingress is optional and opened only for storage work."
spec:
interval: 10m
suspend: true

View File

@ -6,6 +6,7 @@ metadata:
namespace: flux-system
annotations:
kustomize.toolkit.fluxcd.io/ssa: IfNotPresent
atlas.bstein.dev/suspend-reason: "Guardrail rollout is paused until service resource requests are normalized."
spec:
interval: 10m
suspend: true

View File

@ -1,9 +0,0 @@
# clusters/oceanus/flux-system/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
# Populate when oceanus cluster is bootstrapped with Flux.
# - gotk-components.yaml
# - gotk-sync.yaml
- ../platform
- ../applications

View File

@ -1,6 +0,0 @@
# clusters/oceanus/platform/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../infrastructure/modules/base
- ../../infrastructure/modules/profiles/oceanus-validator

View File

@ -0,0 +1,122 @@
# syntax=docker/dockerfile:1
# dockerfiles/Dockerfile.hermes-agent
FROM nousresearch/hermes-agent@sha256:9c841866021c54c4596849f6135717e8a4d52ba510b7f52c50aef1de1a283973
USER root
# Keep dashboard chat sockets tied to the intended React mount and conversation.
# A resumed conversation needs a different PTY attachment key from a fresh chat;
# reconnects to that same conversation must keep using the same key.
RUN node <<'NODE'
const fs = require("node:fs");
const path = "/opt/hermes/web/src/pages/ChatPage.tsx";
let source = fs.readFileSync(path, "utf8");
const socketBefore = [
' const url = await api.buildWsUrl("/api/pty", params);',
' const ws = new WebSocket(url);',
].join("\n");
const socketAfter = [
' const url = await api.buildWsUrl("/api/pty", params);',
' if (unmounting) return;',
' const ws = new WebSocket(url);',
].join("\n");
const attachBefore = ' params.attach = ptyAttachToken(forceFresh);';
const attachAfter = [
' const attachScope = resumeParam',
' ? `resume:${resumeParam}:${scopedProfile ?? ""}`',
' : `fresh:${scopedProfile ?? ""}`;',
' params.attach = `${ptyAttachToken(forceFresh)}:${attachScope}`;',
].join("\n");
if (!source.includes(socketBefore)) {
throw new Error("Hermes ChatPage WebSocket patch context changed");
}
if (!source.includes(attachBefore)) {
throw new Error("Hermes ChatPage PTY attachment patch context changed");
}
source = source.replace(socketBefore, socketAfter);
source = source.replace(attachBefore, attachAfter);
fs.writeFileSync(path, source);
NODE
# The upstream OIDC gate authenticates users but deliberately treats the
# dashboard as one shared workstation. Allow a deployment to narrow that
# workstation to explicit OIDC subjects. Enforce this after normal provider
# verification so a denied account is a 403, not a misleading provider 503.
RUN python - <<'PY'
from pathlib import Path
path = Path("/opt/hermes/hermes_cli/dashboard_auth/middleware.py")
source = path.read_text()
helper_before = '''def _client_ip(request: Request) -> str:
fwd = request.headers.get("x-forwarded-for", "")
if fwd:
return fwd.split(",")[0].strip()
return request.client.host if request.client else ""
'''
helper_after = helper_before + '''def _dashboard_user_allowed(session) -> bool:
"""Apply an optional deployment-level OIDC-subject allowlist."""
import os
allowed = {
value.strip()
for value in os.environ.get(
"HERMES_DASHBOARD_OIDC_ALLOWED_USER_IDS", ""
).split(",")
if value.strip()
}
return not allowed or session.user_id in allowed
def _user_forbidden_response() -> Response:
"""Return an authorization failure without exposing identities."""
return JSONResponse(
{
"error": "forbidden",
"detail": "This Atlas account is not authorized for this dashboard.",
},
status_code=403,
)
'''
refresh_before = ''' new_session, refreshing_provider = refreshed
request.state.session = new_session
response = await call_next(request)
'''
refresh_after = ''' new_session, refreshing_provider = refreshed
if not _dashboard_user_allowed(new_session):
return _user_forbidden_response()
request.state.session = new_session
response = await call_next(request)
'''
final_before = ''' request.state.session = session
return await call_next(request)
'''
final_after = ''' if not _dashboard_user_allowed(session):
return _user_forbidden_response()
request.state.session = session
return await call_next(request)
'''
for before, after, label in (
(helper_before, helper_after, "allowlist helper"),
(refresh_before, refresh_after, "refreshed session"),
(final_before, final_after, "verified session"),
):
if before not in source:
raise SystemExit(f"Hermes dashboard auth {label} patch context changed")
source = source.replace(before, after, 1)
path.write_text(source)
PY
COPY dockerfiles/hermes-session-migrate.py /opt/hermes/bin/hermes-session-migrate
RUN cd /opt/hermes/web \
&& npm run build \
&& grep -Fq 'if (unmounting) return;' src/pages/ChatPage.tsx \
&& grep -Fq 'resume:${resumeParam}' src/pages/ChatPage.tsx \
&& grep -Fq 'HERMES_DASHBOARD_OIDC_ALLOWED_USER_IDS' \
/opt/hermes/hermes_cli/dashboard_auth/middleware.py \
&& chmod 0755 /opt/hermes/bin/hermes-session-migrate

View File

@ -0,0 +1,101 @@
#!/usr/bin/env python3
"""Copy selected legacy Hermes sessions into an isolated user home."""
from __future__ import annotations
import os
from pathlib import Path
import shutil
import sqlite3
from hermes_state import SessionDB
def _copy_if_missing(source: Path, target: Path) -> None:
"""Copy one credential/config file without overwriting user state."""
if source.is_file() and not target.exists():
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(source, target)
def _copy_session(
source: sqlite3.Connection,
target: sqlite3.Connection,
session_id: str,
user_id: str,
) -> bool:
"""Copy a session and its messages, preserving original timestamps."""
source.row_factory = sqlite3.Row
session = source.execute(
"SELECT * FROM sessions WHERE id = ?", (session_id,)
).fetchone()
if session is None:
return False
columns = list(session.keys())
values = [user_id if column == "user_id" else session[column] for column in columns]
placeholders = ", ".join("?" for _ in columns)
target.execute(
f"INSERT OR IGNORE INTO sessions ({', '.join(columns)}) "
f"VALUES ({placeholders})",
values,
)
message_columns = [
row[1] for row in target.execute("PRAGMA table_info(messages)").fetchall()
]
selected_columns = [
column
for column in message_columns
if column in {
row[1]
for row in source.execute("PRAGMA table_info(messages)").fetchall()
}
]
column_sql = ", ".join(selected_columns)
target.execute(
f"INSERT OR IGNORE INTO messages ({column_sql}) "
f"SELECT {column_sql} FROM source_db.messages WHERE session_id = ?",
(session_id,),
)
return True
def main() -> None:
"""Create the isolated home and perform the idempotent session copy."""
source_home = Path(os.environ["HERMES_MIGRATE_SOURCE_HOME"])
target_home = Path(os.environ["HERMES_HOME"])
user_id = os.environ["HERMES_MIGRATE_USER_ID"].strip()
session_ids = [
value.strip()
for value in os.environ.get("HERMES_MIGRATE_SESSION_IDS", "").split(",")
if value.strip()
]
target_home.mkdir(parents=True, exist_ok=True)
(target_home / "home" / ".local" / "bin").mkdir(parents=True, exist_ok=True)
(target_home / "workspace" / "skills").mkdir(parents=True, exist_ok=True)
(target_home / "logs").mkdir(parents=True, exist_ok=True)
for filename in (".env", "auth.json"):
_copy_if_missing(source_home / filename, target_home / filename)
source_db = source_home / "state.db"
target_db = target_home / "state.db"
SessionDB(db_path=target_db).close()
copied = 0
if source_db.is_file() and session_ids:
source = sqlite3.connect(f"file:{source_db}?mode=ro", uri=True)
target = sqlite3.connect(target_db)
target.execute("ATTACH DATABASE ? AS source_db", (str(source_db),))
try:
with target:
for session_id in session_ids:
copied += int(_copy_session(source, target, session_id, user_id))
finally:
target.close()
source.close()
print(f"isolated Hermes home ready; migrated_sessions={copied}")
if __name__ == "__main__":
main()

View File

@ -12,7 +12,7 @@ all:
ansible_host: REPLACE_ME
ansible_user: debian
roleset: minipc_gpu
baremetal:
dedicated_hosts:
hosts:
titan-db:
ansible_host: REPLACE_ME
@ -22,7 +22,3 @@ all:
ansible_host: REPLACE_ME
ansible_user: jump
roleset: jumphost
oceanus:
ansible_host: REPLACE_ME
ansible_user: validator
roleset: validator

View File

@ -14,13 +14,6 @@
- common
- titan_jh
- name: Configure oceanus validator host
hosts: oceanus
gather_facts: true
roles:
- common
- oceanus_base
- name: Prepare hybrid tethys node
hosts: titan-24
gather_facts: true

View File

@ -1,6 +0,0 @@
# hosts/roles/oceanus_base/tasks/main.yaml
---
- name: Placeholder for oceanus base configuration
ansible.builtin.debug:
msg: "Install validator prerequisites and monitoring exporters here."
tags: ['oceanus']

View File

@ -1,4 +1,4 @@
# infrastructure/sources/cert-manager/letsencrypt-prod.yaml
# infrastructure/core/cert-manager/letsencrypt-prod.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:

View File

@ -1,4 +1,4 @@
# infrastructure/sources/cert-manager/letsencrypt.yaml
# infrastructure/core/cert-manager/letsencrypt.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:

View File

@ -11,5 +11,5 @@ resources:
- coredns-deployment.yaml
- ntp-sync-daemonset.yaml
- workload-profiles.yaml
- ../sources/cert-manager/letsencrypt.yaml
- ../sources/cert-manager/letsencrypt-prod.yaml
- cert-manager/letsencrypt.yaml
- cert-manager/letsencrypt-prod.yaml

View File

@ -2,4 +2,4 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../clusters/atlas/flux-system
- ../../clusters/atlas/flux-system

View File

@ -4,8 +4,6 @@ items:
- apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
deployment.kubernetes.io/revision: "4"
name: traefik
namespace: traefik
spec:
@ -73,6 +71,14 @@ items:
node-role.kubernetes.io/worker: "true"
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: hardware
operator: In
values:
- rpi5
- rpi4
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
@ -103,6 +109,12 @@ items:
operator: In
values:
- rpi4
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: traefik
topologyKey: kubernetes.io/hostname
restartPolicy: Always
schedulerName: default-scheduler
serviceAccount: atlas-traefik-ingress-controller

View File

@ -33,7 +33,12 @@ spec:
enabled: false
injector:
enabled: true
replicas: 1
# Two replicas because the webhook is failurePolicy: Ignore. With a
# single replica, any pod created while the injector restarts is
# admitted unmutated: it comes up without its Vault agent sidecar,
# never finds /vault/secrets, and crash-loops indefinitely with no
# indication that injection was skipped. Observed twice on ariadne.
replicas: 2
agentImage:
repository: hashicorp/vault
tag: "1.17.6"

View File

@ -12,7 +12,7 @@ Layout
Regeneration
- Update manifests/docs, then regenerate generated artifacts:
- `python scripts/knowledge_render_atlas.py --write`
- `python scripts/render/knowledge_render_atlas.py --write`
Authoring rules
- Never include secret values. Prefer `secretRef` names or Vault paths like `kv/atlas/...`.

View File

@ -1,8 +1,8 @@
{
"counts": {
"helmrelease_host_hints": 19,
"http_endpoints": 45,
"services": 47,
"workloads": 74
"helmrelease_host_hints": 22,
"http_endpoints": 54,
"services": 70,
"workloads": 100
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1,9 +1,19 @@
flowchart LR
host_agent_bstein_dev["agent.bstein.dev"]
svc_hermes_hermes["hermes/hermes (Service)"]
host_agent_bstein_dev --> svc_hermes_hermes
wl_hermes_hermes["hermes/hermes (Deployment)"]
svc_hermes_hermes --> wl_hermes_hermes
host_auth_bstein_dev["auth.bstein.dev"]
svc_sso_oauth2_proxy["sso/oauth2-proxy (Service)"]
host_auth_bstein_dev --> svc_sso_oauth2_proxy
wl_sso_oauth2_proxy["sso/oauth2-proxy (Deployment)"]
svc_sso_oauth2_proxy --> wl_sso_oauth2_proxy
host_backup_bstein_dev["backup.bstein.dev"]
svc_maintenance_oauth2_proxy_soteria["maintenance/oauth2-proxy-soteria (Service)"]
host_backup_bstein_dev --> svc_maintenance_oauth2_proxy_soteria
wl_maintenance_oauth2_proxy_soteria["maintenance/oauth2-proxy-soteria (Deployment)"]
svc_maintenance_oauth2_proxy_soteria --> wl_maintenance_oauth2_proxy_soteria
host_bstein_dev["bstein.dev"]
svc_bstein_dev_home_bstein_dev_home_frontend["bstein-dev-home/bstein-dev-home-frontend (Service)"]
host_bstein_dev --> svc_bstein_dev_home_bstein_dev_home_frontend
@ -111,6 +121,16 @@ flowchart LR
host_pegasus_bstein_dev --> svc_jellyfin_pegasus
wl_jellyfin_pegasus["jellyfin/pegasus (Deployment)"]
svc_jellyfin_pegasus --> wl_jellyfin_pegasus
host_quality_bstein_dev["quality.bstein.dev"]
svc_quality_oauth2_proxy_sonarqube["quality/oauth2-proxy-sonarqube (Service)"]
host_quality_bstein_dev --> svc_quality_oauth2_proxy_sonarqube
wl_quality_oauth2_proxy_sonarqube["quality/oauth2-proxy-sonarqube (Deployment)"]
svc_quality_oauth2_proxy_sonarqube --> wl_quality_oauth2_proxy_sonarqube
host_recovery_bstein_dev["recovery.bstein.dev"]
svc_maintenance_oauth2_proxy_metis["maintenance/oauth2-proxy-metis (Service)"]
host_recovery_bstein_dev --> svc_maintenance_oauth2_proxy_metis
wl_maintenance_oauth2_proxy_metis["maintenance/oauth2-proxy-metis (Deployment)"]
svc_maintenance_oauth2_proxy_metis --> wl_maintenance_oauth2_proxy_metis
host_scm_bstein_dev["scm.bstein.dev"]
svc_gitea_gitea["gitea/gitea (Service)"]
host_scm_bstein_dev --> svc_gitea_gitea
@ -141,6 +161,20 @@ flowchart LR
host_vault_bstein_dev --> svc_vaultwarden_vaultwarden_service
wl_vaultwarden_vaultwarden["vaultwarden/vaultwarden (Deployment)"]
svc_vaultwarden_vaultwarden_service --> wl_vaultwarden_vaultwarden
host_veles_bstein_dev["veles.bstein.dev"]
svc_veles_veles_frontend["veles/veles-frontend (Service)"]
host_veles_bstein_dev --> svc_veles_veles_frontend
wl_veles_veles_frontend["veles/veles-frontend (Deployment)"]
svc_veles_veles_frontend --> wl_veles_veles_frontend
svc_veles_veles_backend["veles/veles-backend (Service)"]
host_veles_bstein_dev --> svc_veles_veles_backend
wl_veles_veles_backend["veles/veles-backend (Deployment)"]
svc_veles_veles_backend --> wl_veles_veles_backend
host_wolf_bstein_dev["wolf.bstein.dev"]
svc_game_stream_oauth2_proxy_wolf["game-stream/oauth2-proxy-wolf (Service)"]
host_wolf_bstein_dev --> svc_game_stream_oauth2_proxy_wolf
wl_game_stream_oauth2_proxy_wolf["game-stream/oauth2-proxy-wolf (Deployment)"]
svc_game_stream_oauth2_proxy_wolf --> wl_game_stream_oauth2_proxy_wolf
subgraph bstein_dev_home[bstein-dev-home]
svc_bstein_dev_home_bstein_dev_home_frontend
@ -175,6 +209,10 @@ flowchart LR
svc_finance_firefly
wl_finance_firefly
end
subgraph game_stream[game-stream]
svc_game_stream_oauth2_proxy_wolf
wl_game_stream_oauth2_proxy_wolf
end
subgraph gitea[gitea]
svc_gitea_gitea
wl_gitea_gitea
@ -183,6 +221,10 @@ flowchart LR
svc_health_wger
wl_health_wger
end
subgraph hermes[hermes]
svc_hermes_hermes
wl_hermes_hermes
end
subgraph jellyfin[jellyfin]
svc_jellyfin_pegasus
wl_jellyfin_pegasus
@ -204,6 +246,12 @@ flowchart LR
subgraph mailu_mailserver[mailu-mailserver]
svc_mailu_mailserver_mailu_front
end
subgraph maintenance[maintenance]
svc_maintenance_oauth2_proxy_soteria
wl_maintenance_oauth2_proxy_soteria
svc_maintenance_oauth2_proxy_metis
wl_maintenance_oauth2_proxy_metis
end
subgraph nextcloud[nextcloud]
svc_nextcloud_nextcloud
wl_nextcloud_nextcloud
@ -218,6 +266,10 @@ flowchart LR
svc_planka_planka
wl_planka_planka
end
subgraph quality[quality]
svc_quality_oauth2_proxy_sonarqube
wl_quality_oauth2_proxy_sonarqube
end
subgraph sso[sso]
svc_sso_oauth2_proxy
wl_sso_oauth2_proxy
@ -232,3 +284,9 @@ flowchart LR
svc_vaultwarden_vaultwarden_service
wl_vaultwarden_vaultwarden
end
subgraph veles[veles]
svc_veles_veles_frontend
wl_veles_veles_frontend
svc_veles_veles_backend
wl_veles_veles_backend
end

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,74 @@
# Hermes Investigative Triage Demonstration
- Date: 2026-08-05
- Run ID: run_d2f61902c00044948a3912a94e895ab9
- Duration: 128 seconds, status completed
- Usage: {"input_tokens": 200961, "output_tokens": 4370, "total_tokens": 205331}
- Mode: supervised, read-only; approvals `smart` with mutation deny-list; zero mutations, zero denied approvals
- Tool audit (from the run's SSE event stream): 8 skill loads (triage-titan-test-failures pack: orchestrator, jenkins-retained-evidence, platform-quality-metrics, kubernetes-readonly-failure-classifier, flux-git-change-correlation, ...), 4 read-only terminal batches (Ariadne internal API sweep, kubectl logs/jobs/configmap in hermes-triage-demo, VictoriaMetrics queries incl. last_over_time fallback, Flux/Gitea revision correlation)
Unlike the automated loop (where Ariadne supplies a sanitized bundle), this run
was given ONLY an incident ID and gathered all evidence itself — the operator's
triage path executed by the agent. Notable behaviors: honest dead-end reporting
(Jenkins 403 -> pivot to Kubernetes-retained logs; Ariadne audit endpoints 404),
instant-query lookback miss -> switched to last_over_time, causal chain
reconstruction across five sources, and an unprompted, legitimate hardening
suggestion (add a build label to ariadne_hermes_triage_action_total).
## Prompt
```text
Use $triage-titan-test-failures.
Investigate Jenkins incident hermes-triage-demo/10. No evidence bundle is attached this time: gather the evidence yourself with your read-only tools, following the operator's standard triage path:
1. Ariadne is the source of truth: query its internal API (base URL in ARIADNE_BASE_URL, e.g. GET /api/internal/testing/triage/latest and any internal audit endpoints you can reach) for records about this incident.
2. Quality metrics: query VictoriaMetrics (VICTORIA_METRICS_URL) for the series ariadne_hermes_triage_incident{jenkins_job="hermes-triage-demo",build="10"} and ariadne_hermes_triage_action_total, and note what they show.
3. Service health: use read-only kubectl to inspect the hermes-triage-demo namespace (jobs, pods, recent events) and confirm what ran there.
4. Change correlation: check Flux state (kubectl get kustomizations -n flux-system; the flux-system GitRepository revision) for recent changes relevant to this incident.
Then return a concise operator-style report with EXACTLY these sections:
FACTS (each with source and reference), INFERENCES, LIKELY CAUSE, WHAT HAPPENED (was it repaired, by what mechanism, final state), NEXT CHECKS (what a human should verify next), and REPO-SIDE FIXES (if any are warranted).
Read-only throughout. Do not mutate anything. Do not use kubectl apply/delete/patch/scale.
```
## Full agent report
```text
FACTS
Ariadne diagnosis/latest, generated 2026-08-05T21:31:06Z, references only the broad testing bundle and does not identify hermes-triage-demo/10. Ariadnes incident/audit candidate endpoints returned HTTP 404.
Kubernetes logs, hermes-triage-demo/hermes-demo-test-10-dgvpj: build 10 emitted incident_id="hermes-triage-demo/10", classification_hint="demo_fixture_unhealthy", and “expected fixture state healthy; found unhealthy.”
Kubernetes Job status: hermes-demo-test-10 was created 2026-08-05T20:59:47Z and failed; its pod ran on titan-06 and had no restarts or image-pull events.
Kubernetes logs, hermes-demo-repair-10-nkrzz: repair job patched hermes-triage-demo-fixture and reported “fixture state reset to healthy”; the Job completed successfully at 2026-08-05T21:01:06Z.
Kubernetes logs/status, hermes-demo-test-11-hv5mh: follow-up test completed at 2026-08-05T21:01:35Z and reported “fixture state healthy.”
Kubernetes ConfigMap hermes-triage-demo-fixture: current data.state is healthy.
VictoriaMetrics, last_over_time(ariadne_hermes_triage_incident{jenkins_job="hermes-triage-demo",build="10"}[24h]): sample at 2026-08-05T21:41:01Z has resolved=1 and every other lifecycle state, including failed and human_required, at 0. The instantaneous selector returned no result because its samples were outside the default instant-query lookback.
VictoriaMetrics, ariadne_hermes_triage_action_total over 24h: repair_demo_fixture has requested=1, accepted=1, success=1 for instance 10.42.19.143; no build label exists on this counter, so it is correlated by the incident instance and timeline rather than proven per-build.
Flux Kustomization hermes-triage-demo: Ready=True at revision main@sha1:aa36b06b2bc43eda21b36e54a7d312e55d27c370. Flux source GitRepository is Ready=True at that same revision.
Gitea commit 49aab8da3298ebfc1b9edab5ce2e6938ad033a23, “feat(hermes-triage): enable autoremediation for the demo loop,” predates this incident and modifies services/maintenance/apps/ariadne-deployment.yaml. The current revision aa36b06 is an unrelated maintenance image update.
Direct Jenkins GET for /job/hermes-triage-demo/10 API metadata and console returned HTTP 403. The Kubernetes Job logs are therefore the retained direct execution evidence used here.
INFERENCES
The failure was an intentional demo-fixture health assertion failure, not an image, scheduling, node, or storage failure. The failed test, repair, and successful follow-up all ran in sequence; this is a strong causal chain.
The repair was automatically accepted under the previously deployed allowlisted autoremediation path. The repair Job itself performed the ConfigMap patch; Flux did not repair the fixture during this incident.
The unrelated cluster concerns in the Ariadne bundle, including Longhorn probe failures and pending Veles pods, have no evidenced link to this ConfigMap-based test path.
LIKELY CAUSE
The fixture was deliberately or otherwise pre-seeded unhealthy before Jenkins incident hermes-triage-demo/10. The test correctly failed on that condition. Confidence: high.
WHAT HAPPENED
Build 10 failed at 20:59:47Z because the fixture state was unhealthy. The deployed autoremediation mechanism accepted repair_demo_fixture, ran hermes-demo-repair-10, and patched only hermes-triage-demo-fixture to healthy. Test 11 then passed at 21:01:35Z. Final state is repaired/resolved: ConfigMap state=healthy, incident metric resolved=1, repair success counter=1, and the demo Flux Kustomization is Ready at aa36b06.
NEXT CHECKS
Human should verify the current deployed revision and readiness after the next Flux interval:
kubectl -n flux-system get kustomization hermes-triage-demo
kubectl -n flux-system describe gitrepository flux-system
Human should retain the build-10 Jenkins evidence if access is available:
curl -fsS https://ci.bstein.dev/job/hermes-triage-demo/10/api/json
curl -fsS https://ci.bstein.dev/job/hermes-triage-demo/10/consoleText
These currently require Jenkins authentication (HTTP 403).
Human should confirm that the resolved gauge remains the only asserted lifecycle state:
curl -G -fsS --data-urlencode 'query=last_over_time(ariadne_hermes_triage_incident{jenkins_job="hermes-triage-demo",build="10"}[24h])' "$VICTORIA_METRICS_URL/api/v1/query"
REPO-SIDE FIXES
None warranted for this incident. The observed behavior matches the committed demo design: an isolated ConfigMap fixture, one allowlisted repair action, a scoped repair ServiceAccount, and one repair per incident. A future hardening change, if desired, would be to add a build label to ariadne_hermes_triage_action_total so repair counters can be directly attributed to a specific incident rather than correlated by instance and time.
```

View File

@ -0,0 +1,134 @@
# Corrections for `mermaid/TestAutomationV2.mmd`
Written 2026-08-06 against the diagram as it stood that morning. The diagram
is not edited here on purpose: it is being maintained in parallel, so this is
the change list rather than a patch.
The structure holds. The trust boundary, the gate chain, the action registry
and the output taxonomy are all still accurate. What follows is what tonight's
work invalidated.
## Nodes that are now factually wrong
**`collector`** says *"Keeps the head and tail"* and *"Keeps the earliest
useful failure regions"*. Earliest-first selection was the defect: on a long
pipeline the byte budget was consumed entirely by successful tool output, and
the enforced failure never reached Hermes. Replace with:
```
Ariadne evidence collector
Reads the full console up to 2 MB
Ranks failure regions by evidence strength
A definite failure outranks a tool that merely ran
Ignores passing test lines that quote failures
Merges repeats and overlapping context
```
**`repair_fixture`** says *"Creates one scoped Job"*. It no longer does. The
repair is an in-process ConfigMap patch; no pod is created.
**`candidate_files`** says *"Ranks the earliest hints first"*. It now also
reads the failing test and follows its imports to the module under test, which
is what made a pull request on a real service repository possible at all:
```
Candidate file selection
Uses path and line hints from failure regions
Reads the failing test as context
Follows its imports to the module under test
Reading is wider than writing: a test is readable, never patchable
```
**`action_scope`** says *"No real service classification maps to a mutation"*.
No longer true. `retry_transient_infra` is allowlisted for every allowlisted
job, so a real service can now receive an action - a Jenkins rebuild.
**`timings`** - the fixture loop is not four minutes. Measure from the red
build, not from arming, because arming depends on the agent pool:
```
Red -> fixture patched healthy: 25s
Red -> rebuild green and incident resolved: 1m04s
```
**`examples`** - supersede with the stronger 2026-08-06 results, including
`ariadne/409` opening a real pull request with the correct one-line fix.
## Structurally misleading
**The `route` decision reads as three exclusive branches.** It is not. An
escalating incident attempts a code proposal *and* files an issue *and* stays
`human_required`. A pull request rides along on the escalation; it never
replaces the issue. As drawn, a reader concludes one substitutes for the
other.
**`branch_build` overstates coverage.** `hermes-code-demo-branches` only
builds branches in the demo repository. A pull request opened against a real
service repository gets no automatic branch build. Mark it `limited`.
**`alert_output` is stale.** Alerting no longer fires on every escalation.
Two narrow rules remain: a repair that ran and failed, and an escalation
untouched for six hours. The issue is the durable artifact; email is the
exception.
## Missing, and worth adding
- The demo classification and `repair_demo_fixture` are now forbidden on any
job other than `hermes-triage-demo`. Hermes misapplied that label to real
services twice at 0.96 and 0.99 confidence; the gates refused it both times,
and it can no longer be produced at all.
- **A build that never finishes** is escalated once it passes the time cap,
with no model call, because its console is still being written. Before this
existed such a build produced nothing anywhere while holding an agent slot.
- **A failure superseded by a newer build can be skipped entirely.** Detection
reads `lastBuild`, so a red build replaced quickly is never triaged.
## Second pass, after the remaining work landed
Everything previously marked conditional is now done, which changes three
things structurally rather than cosmetically.
**Not every incident reaches Hermes any more.** The diagram's main flow runs
intake -> hermes_plane -> controls -> response, which reads as though every
incident is diagnosed by the model. A build that overruns its time cap is now
escalated by Ariadne alone, with no model call at all, because its console is
still being written and any root cause would be invented. That needs its own
edge from the detector straight to the human path, bypassing `hermes_plane`
and `controls` entirely. It is also the honest picture of the trust boundary:
some conclusions are Ariadne's own observations, and the issue body now says
so rather than crediting a diagnosis that never happened.
**The Jenkins evidence limitation is gone, not narrowed.** Delete
`jenkins_limit` and the "Per-test evidence waits on Jenkins plugins" line from
`current_limits`. `junit` and `pipeline-stage-view` are installed, `testReport`
and `wfapi` return 200, and the bundle now carries `failed_tests` with the
failing test name, class and assertion, plus `first_failed_stage`. The
collector always fetched both and had simply been receiving 404s. The `bundle`
node should list them as populated fields, because they are what the diagnosis
now rests on rather than scraped console text.
**The detector watches branches, not just jobs.** A multibranch project is a
folder with no `lastBuild`; it was returned as skipped on every tick. The
detector now expands a folder into its branch jobs, capped at five, so
`detector` should say it watches configured jobs *and the branches inside
multibranch folders*.
Smaller corrections to the same nodes:
- `evidence_sources` may include a service's own namespace, not only the demo
namespace and `jenkins`, where the job is mapped to one.
- The human path files an issue even for an incident triaged before issue
filing existed, reusing the classification the original diagnosis recorded
so one open issue per job and classification still holds.
- Hermes's credential comes from Vault, not a manually created Secret. If the
diagram ever shows credential provenance, that is now the accurate source.
## Conditional on work that was blocked
`model_gate` should now read `anthropic/claude-opus-5` primary, with
`openai-codex/gpt-5.6-terra` first fallback and local `gpt-oss:20b` second.
`jenkins_limit` and *"Per-test evidence waits on Jenkins plugins"* come out
once the `junit` plugin is live. Note the plugin was never blocked on a core
upgrade as previously recorded: `junit 1369.v15da_00283f06` runs on core
2.528.3, only the latest release requires 2.533.

View File

@ -1,12 +1,14 @@
# Metis (node recovery)
## Node classes (current map)
- rpi5 Ubuntu workers: titan-04,05,06,07,08,09,10,11,20,21 (Ubuntu 24.04.3, k3s agent)
- rpi5 Ubuntu workers: titan-04,05,06,07,08,09,10,11 (Ubuntu 24.04.3, k3s agent)
- rpi5 control-plane: titan-0a/0b/0c (Ubuntu 24.04.1, k3s server, control-plane taint)
- rpi4 Armbian longhorn: titan-13/15/17/19 (Armbian 6.6.x, k3s agent, longhorn disks)
- rpi4 Armbian standard: titan-12/14/18 (Armbian 6.6.x, k3s agent)
- Jetson workers: titan-20/21 (Ubuntu 20.04.6, k3s agent)
- amd64 agents: titan-22/24 (Debian 13, k3s agent)
- External/non-cluster: tethys, titan-db, titan-jh, oceanus/titan-23, future titan-20/21 (when added), plus any newcomers.
- Veles storage/simulation worker: titan-23 (Atlas worker with `oceanus` node-pool labels)
- External/dedicated hosts: tethys, titan-db, titan-jh, plus any newcomers.
## Longhorn disk UUIDs (critical nodes)
- titan-13: /mnt/astreae UUID=6031fa8b-f28c-45c3-b7bc-6133300e07c6 (ext4); /mnt/asteria UUID=cbd4989d-62b5-4741-8b2a-28fdae259cae (ext4)
@ -17,10 +19,9 @@
## Metis repo (~/Development/metis)
- CLI skeleton in Go (`cmd/metis`), inventory loader (`pkg/inventory`), plan builder (`pkg/plan`).
- `inventory.example.yaml` shows expected schema (classes + per-node overlay, Longhorn disks, labels, taints).
- `AGENTS.md` in repo is untracked and holds raw notes.
## Next implementation steps
- Add per-class golden image refs and checksums (Harbor or file://) when ready.
- Implement burn execution: download with checksum, write via dd/etcher-equivalent, mount boot/root to inject hostname/IP/k3s tokens/labels/taints, journald/GC drop-ins, and Longhorn fstab entries. Add Windows writer (diskpart + wmic) and Linux writer (dd + sgdisk) paths.
- Add Keycloak/SSH bootstrap: ensure ssh user, authorized keys, and k3s token/URL injection for agents; control-plane restore path with etcd snapshot selection.
- Add per-host inventory entries for tethys, titan-db, titan-jh, oceanus/titan-23, future 20/21 once audited.
- Add per-host inventory entries for tethys, titan-db, titan-jh, and future dedicated hosts once audited.

View File

@ -3,7 +3,7 @@ title: "CI: Gitea → Jenkins pipeline"
tags: ["atlas", "ci", "gitea", "jenkins"]
owners: ["brad"]
entrypoints: ["scm.bstein.dev", "ci.bstein.dev"]
source_paths: ["services/gitea", "services/jenkins", "scripts/jenkins_cred_sync.sh", "scripts/gitea_cred_sync.sh"]
source_paths: ["services/gitea", "services/jenkins", "scripts/sync/jenkins_cred_sync.sh", "scripts/sync/gitea_cred_sync.sh"]
---
# CI: Gitea → Jenkins pipeline
@ -14,7 +14,7 @@ Atlas uses Gitea for source control and Jenkins for CI. Authentication is via Ke
## Where it is configured
- Gitea manifests: `services/gitea/`
- Jenkins manifests: `services/jenkins/`
- Credential sync helpers: `scripts/gitea_cred_sync.sh`, `scripts/jenkins_cred_sync.sh`
- Credential sync helpers: `scripts/sync/gitea_cred_sync.sh`, `scripts/sync/jenkins_cred_sync.sh`
## What users do (typical flow)
- Create a repo in Gitea.

View File

@ -14,8 +14,8 @@ Bootstrapping risk to remember
- Recovery path: bring control plane and workers up, then locally apply minimal platform stack (`core -> helm -> longhorn -> metallb -> traefik -> vault-csi -> vault-injector -> vault -> postgres -> gitea`), then seed Harbor images onto the Harbor node from a control-host bundle, then resume/reconcile Flux. Harbor is a later recovery stage after storage, Vault, Postgres, and Gitea are back.
Script
- `scripts/cluster_power_recovery.sh`
- `scripts/cluster_power_console.sh`
- `scripts/ops/cluster_power_recovery.sh`
- `scripts/ops/cluster_power_console.sh`
- Modes:
- `prepare`
- `shutdown`
@ -26,21 +26,21 @@ Script
Dry-run examples
- Shutdown preview:
- `scripts/cluster_power_recovery.sh shutdown --skip-etcd-snapshot --skip-drain`
- `scripts/ops/cluster_power_recovery.sh shutdown --skip-etcd-snapshot --skip-drain`
- Startup preview:
- `scripts/cluster_power_recovery.sh startup`
- `scripts/ops/cluster_power_recovery.sh startup`
- Harbor seed preview:
- `scripts/cluster_power_recovery.sh harbor-seed`
- `scripts/ops/cluster_power_recovery.sh harbor-seed`
Execute examples
- Prepare helper image on every node:
- `scripts/cluster_power_recovery.sh prepare --execute`
- `scripts/ops/cluster_power_recovery.sh prepare --execute`
- Seed Harbor runtime images onto `titan-05` from the control-host bundle:
- `scripts/cluster_power_recovery.sh harbor-seed --execute`
- `scripts/ops/cluster_power_recovery.sh harbor-seed --execute`
- Planned shutdown:
- `scripts/cluster_power_recovery.sh shutdown --execute`
- `scripts/ops/cluster_power_recovery.sh shutdown --execute`
- Planned startup (canonical branch):
- `scripts/cluster_power_recovery.sh startup --execute --force-flux-branch main`
- `scripts/ops/cluster_power_recovery.sh startup --execute --force-flux-branch main`
Manual remote console examples
- Canonical operator hosts:
@ -129,7 +129,7 @@ Operational notes
- Longhorn is reconciled before Vault/Postgres/Gitea so storage-backed services are not racing the volume layer.
- Harbor is reconciled after the first critical stateful services.
- Harbor bootstrap is now designed around a control-host bundle:
- Build the Harbor bundle locally with `scripts/build_harbor_bootstrap_bundle.sh`.
- Build the Harbor bundle locally with `scripts/ops/build_harbor_bootstrap_bundle.sh`.
- Stage it on the operator host at `~/.local/share/ananke/bundles/harbor-bootstrap-v2.14.1-arm64.tar.zst`.
- Use `harbor-seed --execute` or a full `startup --execute` to stream/import that bundle onto `titan-05`.
- The Harbor bundle remains arm64-only because Harbor is pinned to arm64 nodes. The node-helper image is multi-arch because Ananke uses it across both arm64 and amd64 nodes during prepare/shutdown operations.

View File

@ -0,0 +1,145 @@
# Hermes Automated Triage Demo — Runbook
How to arm, run, watch, and roll back the automated failure-to-repair demo.
Architecture background: `knowledge/hermes-automated-triage-24h-plan.md`.
## What the loop does
```text
You arm the failure (SEED_FAILURE=true)
-> Jenkins job hermes-triage-demo fails (fixture reads "unhealthy")
-> test-runner pod also writes the incident JSON to stdout
(Fluent Bit ships it to OpenSearch kube-*)
-> Ariadne polls the job every minute, opens incident <job>/<build>
-> Ariadne bundles Jenkins evidence + bounded OpenSearch excerpts
-> Ariadne calls the Hermes Agent API (/v1/runs) with
$triage-titan-test-failures
-> Hermes returns schema-valid diagnosis + requested_action
-> Ariadne authorizes (twelve gates) and, if remediation is enabled,
patches the fixture ConfigMap back to "healthy" in process
-> Ariadne triggers ONE rebuild with SEED_FAILURE=false
-> rebuild passes -> incident resolved
Anything else -> issue in the service repo + human_required metric
```
The model behind Hermes is `anthropic/claude-opus-5` as of 2026-08-06, with
`openai-codex/gpt-5.6-terra` as first fallback and a local `gpt-oss:20b`
behind that, so an expired Anthropic credential degrades rather than stops.
Two behaviours worth knowing before you demo, because both look like nothing
happening:
- **A build that never finishes** is escalated once it passes
`ARIADNE_HERMES_HUNG_BUILD_MINUTES` (default 45). No model is consulted -
the console is still being written - so the issue says only that the build
overran and is holding an agent slot.
- **Alerting no longer fires on every escalation.** The issue in the service
repository is the durable artifact. Email now means either a repair ran and
failed, or an escalation has sat untouched for six hours.
## Arming the demo
Jenkins UI: `https://ci.bstein.dev/job/hermes-triage-demo/` → *Build with
Parameters* → check `SEED_FAILURE` → Build.
CLI (any Jenkins user API token):
```bash
curl -u <user>:<token> -X POST \
"https://ci.bstein.dev/job/hermes-triage-demo/buildWithParameters?SEED_FAILURE=true"
```
That is the only manual step. Everything after the red build is automatic.
Run `scripts/ops/hermes_triage_demo.sh preflight` first. The check that most
often decides whether a rehearsal holds its timings is the agent pool: the
Kubernetes cloud caps concurrent agent pods at `containerCapStr: "5"`, and when
real CI has taken all five the demo build sits in the queue reporting *"All
nodes of label ... are offline"* with no other symptom. Observed 2026-08-06: a
run armed at 02:13:42 did not start seeding until 02:19:37, close to six
minutes of dead air, purely because five other builds held the pool. Wait for a
free slot before starting, or quiesce CI.
## Expected timings (measured live 2026-08-06, Ariadne 0.1.0-402)
Time the demo from the moment the build goes **red**, not from arming. Arming
only queues a Jenkins build, and that leg is at the mercy of the agent pool.
- Red → fixture patched back to healthy: **25s** (≤60 s detection tick, ~16 s
Hermes diagnosis, then a single in-process Kubernetes API call).
- Red → rebuild triggered: **38s**.
- Red → rebuild green and incident resolved: **1m04s**.
The whole automated leg is just over a minute, and there is no silent phase
longer than the detection tick. Budgeted Hermes timeout is 420 s; observed
diagnosis runs are 1521 s.
Arming → red was **6m21s** on this run, but 5m35s of that was queue wait
behind a saturated agent pool. On an idle pool expect roughly 1m45s.
The earlier 2026-08-05 figures (1m15s red→repaired, 4m00s total) were measured
when the repair spawned its own Kubernetes Job. Converting the repair to an
in-process call removed a whole pod launch from the critical path.
## Watching it live
- Jenkins: `https://ci.bstein.dev/job/hermes-triage-demo/` (red build N,
then green build N+1 with `SEED_FAILURE=false`).
- Ariadne incident state:
`GET http://ariadne.maintenance/api/internal/audit/events` (in-cluster) or
`/api/admin/audit/events` (Keycloak JWT) — event types
`hermes_autotriage_incident`, `hermes_autotriage_diagnosis`,
`hermes_autotriage_action`. Status flow:
`detected → diagnosed → repairing → awaiting_rebuild → resolved`.
- Metrics (VictoriaMetrics / Grafana Explore):
`ariadne_hermes_triage_incident{jenkins_job="hermes-triage-demo"}`,
`ariadne_hermes_triage_action_total`,
`ariadne_hermes_triage_duration_seconds`.
- Repair evidence: `kubectl -n hermes-triage-demo get jobs` shows
`hermes-demo-test-<N>` (failed) and `hermes-demo-repair-<N>` (succeeded);
both TTL-clean after 1 h.
- Hermes side: the run appears in the dashboard at
`https://agent.bstein.dev` (session/run history).
- Escalation path: alert `HermesTriageHumanRequired` in vmalert
(`vmalert-atlas-availability` deployment, 1 m interval, `for: 2m`)
fires to Alertmanager for any `human_required` incident. Gauges are
republished every tick from stored incident state (restart-safe), and
the alert self-clears once a newer build of the same job is green.
Note: Alertmanager's default receiver is currently null — the alert is
visible in vmalert/Alertmanager/Grafana but pushes no notification.
## Demonstrating safe escalation (second path)
Any failure that does not match the demo-fixture signature — or any
invalid/low-confidence/unknown-action Hermes response — ends as
`status="human_required"` with **no mutation**. The simplest live demo:
temporarily set `ARIADNE_HERMES_AUTOREMEDIATION_ENABLED=false` (see below)
and arm the failure; Ariadne diagnoses fully but executes nothing, and the
alert fires instead.
## Kill switch and rollback
- Instant behavioral off-switch (Flux-managed, in
`services/maintenance/apps/ariadne-deployment.yaml`):
`ARIADNE_HERMES_AUTOREMEDIATION_ENABLED=false` → diagnose-only.
`ARIADNE_HERMES_AUTOTRIAGE_ENABLED=false` → fully off.
- The automatic loop can only ever: create Jobs named
`hermes-demo-repair-*` in namespace `hermes-triage-demo`, and trigger
rebuilds of allowlisted jobs (`ARIADNE_HERMES_AUTOTRIAGE_JOB_ALLOWLIST`,
currently `hermes-triage-demo` only). One action + one rebuild per
incident, ever (storage-backed idempotency).
- Full teardown: remove `hermes-triage-demo` from
`clusters/atlas/flux-system/applications/kustomization.yaml` (Flux prunes
the namespace) and delete the `pipelineJob('hermes-triage-demo')` block
from `services/jenkins/configmap-jcasc.yaml`.
## Credentials
- Ariadne → Hermes: `Authorization: Bearer` key shared via the
`hermes-api-server-key` Secret present in both `hermes` and `maintenance`
namespaces (Hermes's init container seeds it into the persistent `.env`).
NOTE: currently manually created (Vault migration pending — see plan
handoff); rotating = write new value to both Secrets, restart hermes
deployment and ariadne deployment.
- Ariadne → Jenkins: existing `JENKINS_API_USER/TOKEN` from Vault
(`atlas/maintenance/ariadne-db`).

View File

@ -3,7 +3,7 @@ title: "KB authoring: what to write (and what not to)"
tags: ["atlas", "kb", "runbooks"]
owners: ["brad"]
entrypoints: []
source_paths: ["knowledge/runbooks", "scripts/knowledge_render_atlas.py"]
source_paths: ["knowledge/runbooks", "scripts/render/knowledge_render_atlas.py"]
---
# KB authoring: what to write (and what not to)

View File

@ -1,12 +1,14 @@
# Metis (node recovery)
## Node classes (current map)
- rpi5 Ubuntu workers: titan-04,05,06,07,08,09,10,11,20,21 (Ubuntu 24.04.3, k3s agent)
- rpi5 Ubuntu workers: titan-04,05,06,07,08,09,10,11 (Ubuntu 24.04.3, k3s agent)
- rpi5 control-plane: titan-0a/0b/0c (Ubuntu 24.04.1, k3s server, control-plane taint)
- rpi4 Armbian longhorn: titan-13/15/17/19 (Armbian 6.6.x, k3s agent, longhorn disks)
- rpi4 Armbian standard: titan-12/14/18 (Armbian 6.6.x, k3s agent)
- Jetson workers: titan-20/21 (Ubuntu 20.04.6, k3s agent)
- amd64 agents: titan-22/24 (Debian 13, k3s agent)
- External/non-cluster: tethys, titan-db, titan-jh, oceanus/titan-23, plus any newcomers.
- Veles storage/simulation worker: titan-23 (Atlas worker with `oceanus` node-pool labels)
- External/dedicated hosts: tethys, titan-db, titan-jh, plus any newcomers.
### Jetson nodes (titan-20/21)
- Ubuntu 20.04.6 (Focal), kernel 5.10.104-tegra, CRI containerd 2.0.5-k3s2, arch arm64.
@ -22,13 +24,12 @@
## Metis repo (~/Development/metis)
- CLI skeleton in Go (`cmd/metis`), inventory loader (`pkg/inventory`), plan builder (`pkg/plan`).
- `inventory.example.yaml` shows expected schema (classes + per-node overlay, Longhorn disks, labels, taints).
- `AGENTS.md` in repo is untracked and holds raw notes.
## Next implementation steps
- Add per-class golden image refs and checksums (Harbor or file://) when ready.
- Implement burn execution: download with checksum, write via dd/etcher-equivalent, mount boot/root to inject hostname/IP/k3s tokens/labels/taints, journald/GC drop-ins, and Longhorn fstab entries. Add Windows writer (diskpart + wmic) and Linux writer (dd + sgdisk) paths.
- Add Keycloak/SSH bootstrap: ensure ssh user, authorized keys, and k3s token/URL injection for agents; control-plane restore path with etcd snapshot selection.
- Add per-host inventory entries for tethys, titan-db, titan-jh, oceanus/titan-23, future 20/21 once audited.
- Add per-host inventory entries for tethys, titan-db, titan-jh, and future dedicated hosts once audited.
## Node OS/Kernel/CRI snapshot (Jan 2026)
- titan-04: Ubuntu 24.04.3 LTS, kernel 6.8.0-1031-raspi, CRI containerd://2.0.5-k3s2, arch arm64
@ -55,10 +56,10 @@
- titan-24: Debian 13 (trixie), kernel 6.12.57+deb13-amd64, CRI containerd://2.0.5-k3s2, arch amd64
### External hosts
### Dedicated and special-purpose hosts
- titan-db: Ubuntu 24.10, kernel 6.11.0-1015-raspi, root on /dev/sda2 ext4 (465G), boot vfat /dev/sda1; PostgreSQL service enabled.
- titan-jh: Arch Linux ARM (rolling), kernel 6.18.4-2-rpi, NVMe root ext4 238G (/), boot vfat 512M; ~495 packages installed (pacman -Q).
- titan-23/oceanus: TODO audit (future).
- titan-23: Atlas worker carrying the `oceanus` node-pool labels for Veles storage/simulation placement.
### Control plane Pis (titan-0a/0b/0c)

View File

@ -0,0 +1,503 @@
%% Titan Lab physical hardware architecture.
%% Solid links are physical network, storage, power, or sensor paths.
%% Dashed links are administration, telemetry, or recovery control.
%%{init: {"flowchart": {"defaultRenderer": "elk", "curve": "stepAfter", "nodeSpacing": 30, "rankSpacing": 65, "useMaxWidth": false}, "elk": {"mergeEdges": true, "nodePlacementStrategy": "NETWORK_SIMPLEX", "forceNodeModelOrder": true, "considerModelOrder": "NODES_AND_EDGES"}, "themeVariables": {"background": "#000000"}, "themeCSS": "& { background-color: #000000 !important; }"}}%%
flowchart TB
internet["Internet"]:::external
ac_cloud["AC Infinity account"]:::external
subgraph lab["Titan Lab"]
direction TB
subgraph link_legend["Link colors"]
direction LR
subgraph legend_network["Network"]
direction TB
legend_1g["1 Gbps"]:::legend1g
legend_25g["2.5 Gbps"]:::legend25g
legend_external["External network"]:::legendExternal
end
subgraph legend_resources["Resources"]
direction TB
legend_local["Local attachment"]:::legendLocal
legend_shared["Shared storage"]:::legendShared
legend_gpu["GPU"]:::legendGpu
end
subgraph legend_operations["Operations"]
direction TB
legend_power["Power and NUT"]:::legendPower
legend_telemetry["Telemetry and environment"]:::legendTelemetry
legend_control["Administration and recovery"]:::legendControl
legend_safety["Protective shutdown"]:::legendSafety
end
end
subgraph network["Network fabric"]
direction LR
router["ASUS RT-AX88U<br/>Router"]:::network
cisco["Cisco CBS110-24T<br/>1 Gbps switch"]:::network
trendnet["TRENDnet 9-port<br/>2.5 Gbps switch"]:::network
router ==> cisco
router ==> trendnet
end
subgraph atlas["Atlas Kubernetes cluster"]
direction TB
subgraph control_plane["High-availability control plane, ARM64"]
direction LR
subgraph titan_0a["titan-0a"]
direction TB
t0a_host["Raspberry Pi 5<br/>8 GiB RAM<br/>Ubuntu 24.04"]:::control
t0a_storage["Local<br/>500 GiB boot SSD"]:::localStorage
t0a_host --- t0a_storage
end
subgraph titan_0b["titan-0b"]
direction TB
t0b_host["Raspberry Pi 5<br/>8 GiB RAM<br/>Ubuntu 24.04"]:::control
t0b_storage["Local<br/>500 GiB boot SSD"]:::localStorage
t0b_host --- t0b_storage
end
subgraph titan_0c["titan-0c"]
direction TB
t0c_host["Raspberry Pi 5<br/>8 GiB RAM<br/>Ubuntu 24.04"]:::control
t0c_storage["Local<br/>500 GiB boot SSD"]:::localStorage
t0c_host --- t0c_storage
end
t0a_host --- t0b_host
t0b_host --- t0c_host
end
subgraph workers["Workers"]
direction TB
subgraph pi5_workers["Newer workers, ARM64 Raspberry Pi 5"]
direction TB
subgraph pi5_row_a[" "]
direction LR
subgraph titan_04["titan-04"]
direction TB
t04_host["Raspberry Pi 5<br/>8 GiB RAM<br/>Ubuntu 24.04"]:::pi5
t04_storage["Local<br/>SD root<br/>64 GiB astraios"]:::localStorage
t04_host --- t04_storage
end
subgraph titan_05["titan-05"]
direction TB
t05_host["Raspberry Pi 5<br/>8 GiB RAM<br/>Ubuntu 24.04"]:::pi5
t05_storage["Local<br/>SD root<br/>64 GiB astraios"]:::localStorage
t05_host --- t05_storage
end
subgraph titan_06["titan-06"]
direction TB
t06_host["Raspberry Pi 5<br/>8 GiB RAM<br/>Ubuntu 24.04"]:::pi5
t06_storage["Local<br/>SD root<br/>64 GiB astraios"]:::localStorage
t06_host --- t06_storage
end
end
subgraph pi5_row_b[" "]
direction LR
subgraph titan_07["titan-07"]
direction TB
t07_host["Raspberry Pi 5<br/>8 GiB RAM<br/>Ubuntu 24.04"]:::pi5
t07_storage["Local<br/>SD root<br/>64 GiB astraios"]:::localStorage
t07_host --- t07_storage
end
subgraph titan_08["titan-08"]
direction TB
t08_host["Raspberry Pi 5<br/>8 GiB RAM<br/>Ubuntu 24.04"]:::pi5
t08_storage["Local<br/>SD root<br/>64 GiB astraios"]:::localStorage
t08_host --- t08_storage
end
subgraph titan_11["titan-11"]
direction TB
t11_host["Raspberry Pi 5<br/>8 GiB RAM<br/>Ubuntu 24.04"]:::pi5
t11_storage["Local<br/>SD root<br/>64 GiB astraios"]:::localStorage
t11_host --- t11_storage
end
end
end
subgraph pi4_workers["Older workers, ARM64 Raspberry Pi 4"]
direction TB
subgraph pi4_row_a[" "]
direction LR
subgraph titan_12["titan-12"]
direction TB
t12_host["Raspberry Pi 4<br/>8 GiB RAM<br/>Armbian"]:::pi4
t12_root["Local<br/>SD root"]:::localStorage
t12_host --- t12_root
end
subgraph titan_13["titan-13"]
direction TB
t13_host["Raspberry Pi 4<br/>8 GiB RAM<br/>Armbian"]:::pi4
t13_root["Local<br/>SD root"]:::localStorage
t13_astreae["Longhorn<br/>8 TiB Astreae"]:::sharedStorage
t13_asteria["Longhorn<br/>12 TiB Asteria"]:::sharedStorage
t13_host --- t13_root
t13_host --- t13_astreae
t13_host --- t13_asteria
end
subgraph titan_14["titan-14"]
direction TB
t14_host["Raspberry Pi 4<br/>8 GiB RAM<br/>Armbian"]:::pi4
t14_root["Local<br/>SD root"]:::localStorage
t14_host --- t14_root
end
subgraph titan_15["titan-15"]
direction TB
t15_host["Raspberry Pi 4<br/>8 GiB RAM<br/>Armbian"]:::pi4
t15_root["Local<br/>SD root"]:::localStorage
t15_astreae["Longhorn<br/>8 TiB Astreae"]:::sharedStorage
t15_asteria["Longhorn<br/>12 TiB Asteria"]:::sharedStorage
t15_host --- t15_root
t15_host --- t15_astreae
t15_host --- t15_asteria
end
end
subgraph pi4_row_b[" "]
direction LR
subgraph titan_17["titan-17"]
direction TB
t17_host["Raspberry Pi 4<br/>8 GiB RAM<br/>Armbian"]:::pi4
t17_root["Local<br/>SD root"]:::localStorage
t17_astreae["Longhorn<br/>8 TiB Astreae"]:::sharedStorage
t17_asteria["Longhorn<br/>12 TiB Asteria"]:::sharedStorage
t17_host --- t17_root
t17_host --- t17_astreae
t17_host --- t17_asteria
end
subgraph titan_18["titan-18"]
direction TB
t18_host["Raspberry Pi 4<br/>8 GiB RAM<br/>Armbian"]:::pi4
t18_root["Local<br/>SD root"]:::localStorage
t18_host --- t18_root
end
subgraph titan_19["titan-19"]
direction TB
t19_host["Raspberry Pi 4<br/>8 GiB RAM<br/>Armbian"]:::pi4
t19_root["Local<br/>SD root"]:::localStorage
t19_astreae["Longhorn<br/>8 TiB Astreae"]:::sharedStorage
t19_asteria["Longhorn<br/>12 TiB Asteria"]:::sharedStorage
t19_host --- t19_root
t19_host --- t19_astreae
t19_host --- t19_asteria
end
end
end
subgraph accelerator_workers["Accelerators and high-capacity workers"]
direction TB
subgraph jetson_workers["ARM64 NVIDIA Jetson"]
direction LR
subgraph titan_20["titan-20"]
direction TB
t20_host["Jetson Xavier NX<br/>16 GiB RAM<br/>Ubuntu 20.04<br/>Accelerator"]:::accelerator
t20_gpu["GPU<br/>NVIDIA Xavier"]:::gpu
t20_storage["Local<br/>256 GiB disk"]:::localStorage
t20_host --- t20_gpu
t20_host --- t20_storage
end
subgraph titan_21["titan-21"]
direction TB
t21_host["Jetson Xavier NX<br/>16 GiB RAM<br/>Ubuntu 20.04<br/>Accelerator"]:::accelerator
t21_gpu["GPU<br/>NVIDIA Xavier"]:::gpu
t21_storage["Local<br/>256 GiB disk"]:::localStorage
t21_host --- t21_gpu
t21_host --- t21_storage
end
end
subgraph x86_workers["x86_64 systems"]
direction LR
subgraph titan_22["titan-22"]
direction TB
t22_host["X830 mini PC<br/>Core i9-12900H<br/>32 GiB RAM<br/>Debian 13"]:::accelerator
t22_gpu["GPU<br/>NVIDIA RTX 3050 Ti"]:::gpu
t22_igpu["Intel Iris Xe<br/>Not pooled"]:::localGpu
t22_storage["Local<br/>1 TiB disk"]:::localStorage
t22_host --- t22_gpu
t22_host --- t22_igpu
t22_host --- t22_storage
end
subgraph titan_23["titan-23, Oceanus"]
direction TB
t23_host["EPYC 74F3<br/>24 cores, 48 threads<br/>256 GiB RAM<br/>Debian 13"]:::server
t23_gpu["ASPEED graphics<br/>Not pooled"]:::localGpu
t23_root["Local<br/>1 TiB disk"]:::localStorage
t23_data["Local<br/>4 TiB Veles<br/>4 TiB SUI"]:::localStorage
t23_host --- t23_gpu
t23_host --- t23_root
t23_host --- t23_data
end
subgraph titan_24["titan-24, Tethys"]
direction TB
t24_host["Ryzen 9 3900X<br/>24 threads, 64 GiB RAM<br/>Debian 13"]:::accelerator
t24_gpu["GPU<br/>NVIDIA RTX 3080"]:::gpu
t24_root["Local<br/>500 GiB disk"]:::localStorage
t24_data["Local<br/>500 GiB home<br/>500 GiB temporary"]:::localStorage
t24_host --- t24_gpu
t24_host --- t24_root
t24_host --- t24_data
end
end
end
pi5_workers ~~~ pi4_workers
pi4_workers ~~~ accelerator_workers
end
subgraph virtual_resources["Virtual resource pools"]
direction LR
subgraph cluster_storage["Longhorn storage classes"]
direction LR
astreae["Astreae<br/>4 x 8 TiB<br/>32 TiB"]:::sharedPool
asteria["Asteria<br/>4 x 12 TiB<br/>48 TiB"]:::sharedPool
end
subgraph cluster_gpus["Kubernetes GPU pool"]
direction LR
arm_gpu_pool["NVIDIA ARM64<br/>titan-20 and titan-21"]:::gpuPool
x86_gpu_pool["NVIDIA x86_64<br/>titan-22 and titan-24"]:::gpuPool
end
end
subgraph cluster_telemetry["Cluster telemetry"]
direction LR
typhon["Typhon<br/>Kubernetes workload"]:::clusterService
victoria["VictoriaMetrics"]:::telemetry
grafana["Grafana"]:::telemetry
typhon ==>|environment metrics| victoria
victoria --> grafana
end
end
subgraph outside_cluster["Lab systems outside the Atlas cluster"]
direction TB
subgraph support_hosts["Support and host-level services"]
direction LR
subgraph titan_db["titan-db"]
direction TB
tdb_host["Raspberry Pi 5<br/>8 GiB RAM<br/>Ubuntu 24.04"]:::support
tdb_storage["Local<br/>500 GiB boot SSD"]:::localStorage
tdb_postgres["PostgreSQL<br/>HA control-plane database"]:::hostSoftware
ananke_db["NUT and Ananke<br/>Host services<br/>Automatic recovery"]:::hostService
tdb_host --- tdb_storage
tdb_host --- tdb_postgres
tdb_host --- ananke_db
end
subgraph titan_jh["titan-jh"]
direction TB
tjh_host["Raspberry Pi 5<br/>8 GiB RAM<br/>Arch Linux ARM<br/>Jump host"]:::support
tjh_storage["Local<br/>256 GiB boot SSD"]:::localStorage
lesavka["Lesavka<br/>Remote control for Tethys"]:::hostService
tjh_host --- tjh_storage
tjh_host --- lesavka
end
ananke_t24["NUT and Ananke on titan-24<br/>Host services outside Kubernetes<br/>UPS telemetry and safe shutdown"]:::hostService
end
subgraph failed_hardware["Boards awaiting replacement"]
direction LR
titan_09["titan-09<br/>Raspberry Pi 5"]:::failed
titan_10["titan-10<br/>Raspberry Pi 5"]:::failed
titan_16["titan-16<br/>Raspberry Pi 4"]:::failed
end
end
subgraph facilities["Power and enclosure"]
direction LR
subgraph protected_power["UPS protection"]
direction LR
subgraph pyrphoros_power["Pyrphoros"]
direction TB
pyrphoros["CyberPower<br/>850 VA UPS"]:::power
pyrphoros_loads["Protected loads<br/>Router and Cisco switch<br/>Control plane and Pi workers<br/>Jetsons, titan-22, titan-db<br/>Cooling equipment"]:::powerLoad
pyrphoros --> pyrphoros_loads
end
subgraph statera_power["Statera"]
direction TB
statera["CyberPower<br/>1500PFCLCD"]:::power
statera_loads["Protected loads<br/>TRENDnet switch<br/>titan-23, titan-24, titan-jh"]:::powerLoad
statera --> statera_loads
end
end
subgraph cooling["Enclosure cooling"]
direction TB
enclosure["Filtered equipment tent<br/>AC-cooled intake<br/>Controlled exhaust"]:::environment
ac_controller["AC Infinity controller<br/>Temperature<br/>Humidity<br/>Pressure"]:::environment
fans["Controlled intake<br/>and exhaust fans"]:::environment
enclosure --- ac_controller
ac_controller --> fans
end
end
cisco ==> control_plane
cisco ==> pi5_workers
cisco ==> pi4_workers
cisco ==> jetson_workers
cisco ==> titan_db
trendnet ==> titan_jh
trendnet ==> x86_workers
t0b_host -.->|HA PostgreSQL| tdb_postgres
tjh_host -.->|administration| control_plane
tjh_host -.->|administration| workers
lesavka -.->|remote control| t24_host
t13_astreae ==> astreae
t15_astreae ==> astreae
t17_astreae ==> astreae
t19_astreae ==> astreae
t13_asteria ==> asteria
t15_asteria ==> asteria
t17_asteria ==> asteria
t19_asteria ==> asteria
t20_gpu ==> arm_gpu_pool
t21_gpu ==> arm_gpu_pool
t22_gpu ==> x86_gpu_pool
t24_gpu ==> x86_gpu_pool
t24_host -.->|same physical host| ananke_t24
pyrphoros -->|NUT| ananke_db
statera -->|NUT| ananke_t24
ananke_db -.->|power metrics| victoria
ananke_t24 -.->|power metrics| victoria
ananke_db -.->|database and cluster recovery| t0a_host
ananke_t24 -.->|low-battery shutdown| control_plane
ac_controller --> ac_cloud
ac_cloud -.->|environment data| typhon
end
internet --> router
classDef external fill:#292d33,stroke:#a0a8b3,color:#ffffff,stroke-width:2px
classDef network fill:#17324a,stroke:#65b5e8,color:#ffffff,stroke-width:2px
classDef control fill:#163e48,stroke:#65d2df,color:#ffffff,stroke-width:2px
classDef pi5 fill:#173d2a,stroke:#68d391,color:#ffffff,stroke-width:2px
classDef pi4 fill:#293b23,stroke:#9acb70,color:#ffffff,stroke-width:2px
classDef accelerator fill:#382b50,stroke:#b49aef,color:#ffffff,stroke-width:2px
classDef server fill:#3c3046,stroke:#c4a4dc,color:#ffffff,stroke-width:2px
classDef support fill:#293845,stroke:#9fc5dc,color:#ffffff,stroke-width:2px
classDef localStorage fill:#1d3040,stroke:#6f9fbd,color:#ffffff
classDef sharedStorage fill:#493b18,stroke:#efc65b,color:#ffffff,stroke-width:2px
classDef sharedPool fill:#594817,stroke:#ffd166,color:#ffffff,stroke-width:3px
classDef gpu fill:#482c5f,stroke:#d0a6ff,color:#ffffff,stroke-width:2px
classDef localGpu fill:#292d35,stroke:#9ba4b2,color:#ffffff
classDef gpuPool fill:#573476,stroke:#d8b4fe,color:#ffffff,stroke-width:3px
classDef clusterService fill:#153c38,stroke:#5fc9bc,color:#ffffff,stroke-width:2px
classDef telemetry fill:#203651,stroke:#67a9e9,color:#ffffff,stroke-width:2px
classDef hostSoftware fill:#243a45,stroke:#73b8d0,color:#ffffff,stroke-width:2px
classDef hostService fill:#48351c,stroke:#e3ae59,color:#ffffff,stroke-width:2px
classDef failed fill:#3d1f25,stroke:#f06b78,color:#ffffff,stroke-width:2px,stroke-dasharray:8 6
classDef power fill:#49301c,stroke:#ec9f55,color:#ffffff,stroke-width:2px
classDef powerLoad fill:#3b2d20,stroke:#c98e55,color:#ffffff
classDef environment fill:#153d3a,stroke:#58c9bd,color:#ffffff,stroke-width:2px
classDef legend1g fill:#0b1726,stroke:#4ea1ff,color:#ffffff,stroke-width:3px
classDef legend25g fill:#082027,stroke:#22d3ee,color:#ffffff,stroke-width:3px
classDef legendExternal fill:#17162c,stroke:#818cf8,color:#ffffff,stroke-width:3px
classDef legendLocal fill:#171b20,stroke:#a8b3c2,color:#ffffff,stroke-width:3px
classDef legendShared fill:#2f270d,stroke:#f6c453,color:#ffffff,stroke-width:3px
classDef legendGpu fill:#271536,stroke:#c084fc,color:#ffffff,stroke-width:3px
classDef legendPower fill:#301b0c,stroke:#fb923c,color:#ffffff,stroke-width:3px
classDef legendTelemetry fill:#0b2917,stroke:#4ade80,color:#ffffff,stroke-width:3px
classDef legendControl fill:#32132a,stroke:#f472b6,color:#ffffff,stroke-width:3px
classDef legendSafety fill:#321217,stroke:#fb7185,color:#ffffff,stroke-width:3px
style lab fill:#020202,stroke:#89919c,stroke-width:4px
style link_legend fill:#050505,stroke:#89919c,stroke-width:2px
style legend_network fill:#03070b,stroke:#4ea1ff,stroke-width:1px
style legend_resources fill:#080706,stroke:#f6c453,stroke-width:1px
style legend_operations fill:#080506,stroke:#f472b6,stroke-width:1px
style network fill:#03070b,stroke:#65b5e8,stroke-width:3px
style atlas fill:#020608,stroke:#65d2df,stroke-width:4px
style control_plane fill:#061014,stroke:#65d2df,stroke-width:3px
style workers fill:#030603,stroke:#72b879,stroke-width:3px
style pi5_workers fill:#050d08,stroke:#68d391,stroke-width:2px
style pi5_row_a fill:transparent,stroke:transparent
style pi5_row_b fill:transparent,stroke:transparent
style pi4_workers fill:#080d05,stroke:#9acb70,stroke-width:2px
style pi4_row_a fill:transparent,stroke:transparent
style pi4_row_b fill:transparent,stroke:transparent
style accelerator_workers fill:#09060d,stroke:#b49aef,stroke-width:2px
style jetson_workers fill:#0b0710,stroke:#b49aef,stroke-width:2px
style x86_workers fill:#0b0710,stroke:#c4a4dc,stroke-width:2px
style virtual_resources fill:#08080b,stroke:#a8a2b3,stroke-width:3px
style cluster_storage fill:#100d05,stroke:#ffd166,stroke-width:2px
style cluster_gpus fill:#0d0813,stroke:#d8b4fe,stroke-width:2px
style cluster_telemetry fill:#04100f,stroke:#5fc9bc,stroke-width:2px
style outside_cluster fill:#07090b,stroke:#9fc5dc,stroke-width:3px
style support_hosts fill:#07090b,stroke:#9fc5dc,stroke-width:2px
style failed_hardware fill:#0d0708,stroke:#f06b78,stroke-width:2px,stroke-dasharray:8 6
style facilities fill:#070604,stroke:#ec9f55,stroke-width:3px
style protected_power fill:#0e0905,stroke:#ec9f55,stroke-width:2px
style pyrphoros_power fill:#100b06,stroke:#d99a58,stroke-width:2px
style statera_power fill:#100b06,stroke:#d99a58,stroke-width:2px
style cooling fill:#04100f,stroke:#58c9bd,stroke-width:2px
linkStyle 0,54,55,56,57,58,59 stroke:#4ea1ff,color:#4ea1ff,stroke-width:3px
linkStyle 1,60 stroke:#22d3ee,color:#22d3ee,stroke-width:4px
linkStyle 86 stroke:#818cf8,color:#818cf8,stroke-width:3px
linkStyle 2,3,4,7,8,9,10,11,12,13,14,17,18,21,24,25,29,31,34,36,37,39,40,45,48 stroke:#a8b3c2,color:#a8b3c2,stroke-width:2px
linkStyle 15,16,19,20,22,23,26,27,65,66,67,68,69,70,71,72 stroke:#f6c453,color:#f6c453,stroke-width:3px
linkStyle 28,30,32,33,35,38,73,74,75,76 stroke:#c084fc,color:#c084fc,stroke-width:3px
linkStyle 50,51,78,79 stroke:#fb923c,color:#fb923c,stroke-width:3px
linkStyle 43,44,52,53,80,81,84,85 stroke:#4ade80,color:#4ade80,stroke-width:3px
linkStyle 5,6,46,47,49,61,62,63,64,77,82 stroke:#f472b6,color:#f472b6,stroke-width:2px
linkStyle 83 stroke:#fb7185,color:#fb7185,stroke-width:4px

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 724 KiB

View File

@ -0,0 +1,705 @@
%% Atlas service access architecture.
%% Domains, identity profiles, entry services, and selected internal dependencies.
%% Node placement and short labels carry the detail.
%%{init: {"flowchart": {"defaultRenderer": "elk", "curve": "stepAfter", "nodeSpacing": 30, "rankSpacing": 65, "useMaxWidth": false}, "elk": {"mergeEdges": true, "nodePlacementStrategy": "LINEAR_SEGMENTS", "forceNodeModelOrder": true, "considerModelOrder": "NODES_AND_EDGES"}, "themeVariables": {"background": "#000000", "lineColor": "#8b95a5", "primaryTextColor": "#ffffff", "clusterBkg": "#03040d", "clusterBorder": "#6d7485"}, "themeCSS": "& { background-color: #000000 !important; }"}}%%
flowchart TB
subgraph system["Atlas Service Access Architecture"]
direction TB
subgraph legend["Legend"]
direction LR
subgraph node_key["Nodes"]
direction LR
key_domain["DNS name"]:::domain
key_access["OIDC access"]:::access
key_oauth["Access proxy"]:::oauth
key_directory["LDAP or synced account"]:::directory
key_entry["Entry service"]:::user
key_internal["Internal service"]:::support
key_data["Data service"]:::data
key_job["Job or worker"]:::ephemeral
key_external["Outside Kubernetes"]:::external
end
subgraph link_key["Links"]
direction LR
key_ingress["Blue<br/>Domain to identity"]:::linkIngress
key_service["Green<br/>Identity to service"]:::linkService
key_direct["Gold<br/>Direct access"]:::linkDirect
key_auth["Purple<br/>Identity control"]:::linkAuth
key_data_link["Violet<br/>Data flow"]:::linkData
key_telemetry["Teal<br/>Telemetry"]:::linkTelemetry
key_control["Orange<br/>Delivery and control"]:::linkControl
key_internal_link["Gray<br/>Internal flow"]:::linkInternal
end
end
subgraph application_band["Applications and Communications"]
direction LR
subgraph application_domains["Domains"]
direction TB
portal_domain["bstein.dev"]:::domain
portal_chat_domain["chat.ai.bstein.dev"]:::domain
cassandra_domain["cassandra.bstein.dev"]:::domain
element_domain["live.bstein.dev"]:::domain
matrix_domain["matrix.live.bstein.dev"]:::domain
call_domain["call.live.bstein.dev"]:::domain
livekit_domain["kit.live.bstein.dev"]:::domain
turn_domain["turn.live.bstein.dev"]:::domain
end
subgraph application_access["Keycloak Access"]
direction LR
subgraph application_atlas_access["atlas realm"]
direction TB
portal_oidc_access["Portal API<br/>OIDC token"]:::access
portal_chat_oidc_access["Portal Chat<br/>OIDC"]:::access
element_oidc_access["Element Web<br/>OIDC through MAS"]:::access
matrix_oidc_access["Matrix<br/>OIDC through MAS"]:::access
end
subgraph application_cassandra_access["cassandra realm"]
direction TB
cassandra_oidc_access["Cassandra<br/>OIDC"]:::access
end
end
subgraph application_services["Cluster Services"]
direction TB
application_delivery["Flux delivery<br/>Application workloads"]:::control
application_state_clients["Shared PostgreSQL<br/>Portal and Matrix"]:::control
subgraph website_ns["bstein-dev-home"]
direction LR
website_frontend["Portal"]:::user
website_chat_gateway["Chat gateway"]:::user
website_backend["Backend"]:::support
website_frontend --> website_backend
website_chat_gateway --> website_backend
end
subgraph cassandra_ns["cassandra"]
direction LR
cassandra_frontend["Cassandra UI"]:::user
cassandra_backend["API"]:::support
cassandra_postgres["PostgreSQL"]:::data
cassandra_generator["Generator"]:::ephemeral
cassandra_simulation["Simulation jobs"]:::ephemeral
cassandra_retention["Retention job"]:::ephemeral
cassandra_artifacts["Artifacts"]:::data
cassandra_frontend --> cassandra_backend
cassandra_backend --> cassandra_postgres
cassandra_generator --> cassandra_backend
cassandra_simulation --> cassandra_backend
cassandra_simulation --> cassandra_artifacts
cassandra_retention --> cassandra_artifacts
end
subgraph comms_ns["comms"]
direction LR
comms_element_web["Element Web"]:::user
comms_element_call["Element Call"]:::user
comms_wellknown["Matrix discovery"]:::support
comms_guest["Guest registration"]:::support
comms_mas["Matrix auth service"]:::support
comms_synapse["Synapse"]:::support
comms_replication["Synapse replication"]:::support
comms_redis["Redis"]:::data
comms_livekit_token["LiveKit token service"]:::support
comms_livekit["LiveKit"]:::support
comms_coturn["Coturn"]:::support
comms_atlasbot["Atlasbot"]:::support
comms_element_web --> comms_mas
comms_element_web --> comms_synapse
comms_element_call --> comms_livekit_token
comms_livekit_token --> comms_livekit
comms_mas --> comms_synapse
comms_synapse --> comms_replication
comms_synapse --> comms_redis
comms_guest --> comms_synapse
comms_atlasbot --> comms_synapse
end
end
portal_domain portal_identity@-->|HTTPS| portal_oidc_access
portal_oidc_access portal_entry@--> website_frontend
portal_chat_domain portal_chat_identity@-->|HTTPS| portal_chat_oidc_access
portal_chat_oidc_access portal_chat_entry@--> website_chat_gateway
cassandra_domain cassandra_identity@-->|HTTPS| cassandra_oidc_access
cassandra_oidc_access cassandra_entry@--> cassandra_frontend
element_domain element_identity@-->|HTTPS| element_oidc_access
element_oidc_access element_entry@--> comms_element_web
matrix_domain matrix_identity@-->|HTTPS| matrix_oidc_access
matrix_oidc_access matrix_entry@--> comms_synapse
matrix_domain matrix_api@-->|Matrix API| comms_synapse
call_domain call_entry@-->|HTTPS| comms_element_call
livekit_domain livekit_entry@-->|HTTPS and WebRTC| comms_livekit
turn_domain turn_entry@-->|TURN| comms_coturn
end
subgraph personal_band["Personal Services"]
direction LR
subgraph personal_domains["Domains"]
direction LR
subgraph personal_domains_a["Accounts and Health"]
direction TB
budget_domain["budget.bstein.dev"]:::domain
firefly_domain["money.bstein.dev"]:::domain
wolf_domain["wolf.bstein.dev"]:::domain
moonlight_domain["moonlight.bstein.dev"]:::domain
wger_domain["health.bstein.dev"]:::domain
chat_domain["chat.bstein.dev"]:::domain
end
subgraph personal_domains_b["Media and Productivity"]
direction TB
jellyfin_domain["stream.bstein.dev"]:::domain
pegasus_domain["pegasus.bstein.dev"]:::domain
mailu_domain["mail.bstein.dev"]:::domain
cloud_domain["cloud.bstein.dev"]:::domain
office_domain["office.bstein.dev"]:::domain
outline_domain["notes.bstein.dev"]:::domain
planka_domain["tasks.bstein.dev"]:::domain
vaultwarden_domain["vault.bstein.dev"]:::domain
monero_domain["monero.bstein.dev"]:::domain
end
end
subgraph personal_access["atlas realm and directory access"]
direction LR
subgraph personal_access_a["Accounts"]
direction TB
budget_oidc_access["Actual Budget<br/>OpenID"]:::access
firefly_synced_access["Firefly<br/>Synced account"]:::directory
wolf_oauth_access["Wolf<br/>Access proxy"]:::oauth
wger_synced_access["Wger<br/>Synced account"]:::directory
chat_oidc_access["Hermes Chat<br/>OIDC"]:::access
end
subgraph personal_access_b["Media and Productivity"]
direction TB
jellyfin_ldap_access["Jellyfin<br/>LDAP"]:::directory
cloud_oidc_access["Nextcloud<br/>OIDC"]:::access
outline_oidc_access["Outline<br/>OIDC"]:::access
planka_oidc_access["Planka<br/>OIDC"]:::access
end
end
subgraph personal_services["Cluster Services"]
direction LR
personal_delivery["Flux delivery<br/>Personal workloads"]:::control
personal_state_clients["Shared PostgreSQL<br/>Personal services"]:::control
subgraph personal_services_a["Accounts, Health, and Media"]
direction TB
subgraph finance_ns["finance"]
direction LR
finance_budget["Actual Budget"]:::user
finance_firefly["Firefly"]:::user
finance_sync["Account sync"]:::ephemeral
finance_import["Import job"]:::ephemeral
finance_sync --> finance_firefly
finance_import --> finance_firefly
end
subgraph game_ns["game-stream"]
direction LR
game_proxy["Wolf access proxy"]:::oauth
game_manager["Wolf manager"]:::support
game_api["Wolf API"]:::support
game_wolf["Wolf"]:::support
game_gatekeeper["Gatekeeper"]:::support
game_moonlight["Moonlight"]:::user
game_proxy --> game_manager
game_manager --> game_api
game_api --> game_wolf
game_gatekeeper --> game_wolf
game_moonlight --> game_wolf
end
subgraph health_ns["health"]
direction LR
health_wger["Wger"]:::user
health_sync["Account sync"]:::ephemeral
health_admin["Admin sync"]:::ephemeral
health_sync --> health_wger
health_admin --> health_wger
end
subgraph hermes_chat_ns["hermes-chat"]
direction LR
hermes_chat["Hermes Chat"]:::user
end
subgraph jellyfin_ns["jellyfin"]
direction LR
streaming_pegasus["Pegasus"]:::user
streaming_jellyfin["Jellyfin"]:::user
streaming_pegasus --> streaming_jellyfin
end
end
subgraph personal_services_b["Mail and Productivity"]
direction TB
subgraph mail_ns["mailu-mailserver"]
direction LR
mail_front["Mail web"]:::user
mail_protocols["Mail protocols"]:::user
mail_admin["Admin"]:::support
mail_dovecot["Dovecot"]:::support
mail_postfix["Postfix"]:::support
mail_rspamd["Rspamd"]:::support
mail_clamav["ClamAV"]:::support
mail_extractors["Tika and oletools"]:::support
mail_redis["Redis"]:::data
mail_front --> mail_admin
mail_protocols --> mail_dovecot
mail_protocols --> mail_postfix
mail_postfix --> mail_rspamd
mail_rspamd --> mail_clamav
mail_rspamd --> mail_extractors
mail_admin --> mail_redis
end
subgraph cloud_ns["nextcloud"]
direction LR
cloud_nextcloud["Nextcloud"]:::user
cloud_collabora["Collabora"]:::support
cloud_mail_sync["Mail sync"]:::ephemeral
cloud_maintenance["Maintenance job"]:::ephemeral
cloud_nextcloud --> cloud_collabora
cloud_mail_sync --> cloud_nextcloud
cloud_maintenance --> cloud_nextcloud
end
subgraph notes_tasks_ns["Notes and Tasks"]
direction LR
outline_service["Outline"]:::user
outline_redis["Redis"]:::data
planka_service["Planka"]:::user
outline_service --> outline_redis
end
subgraph vaultwarden_ns["vaultwarden"]
direction LR
vaultwarden_service["Vaultwarden"]:::user
end
subgraph crypto_ns["crypto"]
direction LR
crypto_wallet["Wallet RPC"]:::support
crypto_p2pool["P2Pool"]:::support
crypto_monerod["Monerod"]:::user
crypto_wallet --> crypto_monerod
crypto_p2pool --> crypto_monerod
end
end
end
budget_domain budget_identity@-->|HTTPS| budget_oidc_access
budget_oidc_access budget_entry@--> finance_budget
firefly_domain firefly_identity@-->|HTTPS| firefly_synced_access
firefly_synced_access firefly_entry@--> finance_firefly
wolf_domain wolf_identity@-->|HTTPS| wolf_oauth_access
wolf_oauth_access wolf_entry@--> game_manager
moonlight_domain moonlight_entry@-->|Streaming| game_moonlight
wger_domain wger_identity@-->|HTTPS| wger_synced_access
wger_synced_access wger_entry@--> health_wger
chat_domain chat_identity@-->|HTTPS| chat_oidc_access
chat_oidc_access chat_entry@--> hermes_chat
jellyfin_domain jellyfin_identity@-->|HTTPS| jellyfin_ldap_access
jellyfin_ldap_access jellyfin_entry@--> streaming_jellyfin
pegasus_domain pegasus_entry@-->|HTTPS| streaming_pegasus
mailu_domain mail_web_entry@-->|HTTPS| mail_front
mailu_domain mail_protocol_entry@-->|Mail protocols| mail_protocols
cloud_domain cloud_identity@-->|HTTPS| cloud_oidc_access
cloud_oidc_access cloud_entry@--> cloud_nextcloud
office_domain office_entry@-->|WOPI| cloud_collabora
outline_domain outline_identity@-->|HTTPS| outline_oidc_access
outline_oidc_access outline_entry@--> outline_service
planka_domain planka_identity@-->|HTTPS| planka_oidc_access
planka_oidc_access planka_entry@--> planka_service
vaultwarden_domain vaultwarden_entry@-->|HTTPS and local auth| vaultwarden_service
monero_domain monero_entry@-->|RPC| crypto_monerod
end
subgraph platform_band["Platform Services"]
direction LR
subgraph platform_domains["Domains"]
direction LR
subgraph platform_domains_a["Delivery"]
direction TB
gitops_domain["cd.bstein.dev"]:::domain
gitea_domain["scm.bstein.dev"]:::domain
harbor_domain["registry.bstein.dev"]:::domain
hermes_domain["agent.bstein.dev"]:::domain
jenkins_domain["ci.bstein.dev"]:::domain
end
subgraph platform_domains_b["Operations"]
direction TB
logs_domain["logs.bstein.dev"]:::domain
longhorn_domain["longhorn.bstein.dev"]:::domain
grafana_domain["metrics.bstein.dev"]:::domain
alerts_domain["alerts.bstein.dev"]:::domain
quality_domain["quality.bstein.dev"]:::domain
keycloak_domain["sso.bstein.dev"]:::domain
vault_domain["secret.bstein.dev"]:::domain
end
end
subgraph platform_access["Keycloak Access"]
direction LR
subgraph platform_access_a["atlas realm delivery"]
direction TB
gitops_oidc_access["Weave GitOps<br/>OIDC"]:::access
gitea_atlas_oidc_access["Gitea<br/>atlas realm"]:::access
harbor_oidc_access["Harbor UI<br/>OIDC"]:::access
hermes_oidc_access["Hermes Agent<br/>OIDC"]:::access
jenkins_oidc_access["Jenkins<br/>OIDC"]:::access
end
subgraph platform_access_b["atlas realm operations"]
direction TB
logs_oauth_access["OpenSearch Dashboards<br/>Access proxy"]:::oauth
longhorn_oauth_access["Longhorn<br/>Admin proxy"]:::oauth
grafana_oidc_access["Grafana<br/>OIDC"]:::access
quality_oauth_access["SonarQube<br/>Admin and developer proxy"]:::oauth
keycloak_admin_access["Keycloak console<br/>Realm admin"]:::access
vault_oidc_access["Vault<br/>Admin OIDC"]:::access
end
subgraph platform_access_c["cassandra realm client"]
direction TB
gitea_cassandra_oidc_access["Gitea<br/>OIDC"]:::access
end
end
subgraph platform_services["Cluster Services"]
direction LR
platform_delivery["Flux delivery<br/>Platform workloads"]:::control
platform_state_clients["Shared PostgreSQL<br/>Platform services"]:::control
subgraph delivery_services["Delivery and Automation"]
direction TB
subgraph flux_ns["flux-system"]
direction LR
flux_source["Source controller"]:::support
flux_kustomize["Kustomize controller"]:::support
flux_helm["Helm controller"]:::support
flux_image_reflector["Image reflector"]:::support
flux_image_automation["Image automation"]:::support
flux_notification["Notification controller"]:::support
flux_webhook["Webhook receiver"]:::support
flux_weave["Weave GitOps"]:::user
flux_source --> flux_kustomize
flux_source --> flux_helm
flux_image_reflector --> flux_image_automation
flux_webhook --> flux_notification
flux_weave --> flux_source
end
subgraph gitea_ns["gitea"]
direction LR
gitea_http["Gitea web"]:::user
gitea_ssh["Gitea SSH"]:::support
end
subgraph harbor_ns["harbor"]
direction LR
harbor_portal["Harbor portal"]:::user
harbor_core["Harbor core"]:::support
harbor_jobservice["Job service"]:::support
harbor_registry["OCI registry"]:::data
harbor_redis["Redis"]:::data
harbor_portal --> harbor_core
harbor_jobservice --> harbor_core
harbor_core --> harbor_registry
harbor_core --> harbor_redis
end
subgraph hermes_ns["hermes"]
direction LR
hermes_agent["Hermes Agent"]:::user
hermes_model_gate["Model gate"]:::support
hermes_gpt_oss["gpt-oss:20b<br/>Fallback model"]:::support
hermes_agent --> hermes_model_gate
hermes_model_gate --> hermes_gpt_oss
end
subgraph jenkins_ns["jenkins"]
direction LR
jenkins_service["Jenkins"]:::user
jenkins_agents["Build agents"]:::ephemeral
jenkins_service --> jenkins_agents
end
end
subgraph operations_services["Operations"]
direction TB
subgraph logging_ns["logging"]
direction LR
logging_oauth_proxy["Dashboard proxy"]:::oauth
logging_dashboards["OpenSearch Dashboards"]:::user
logging_opensearch["OpenSearch"]:::data
logging_fluent_bit["Fluent Bit"]:::support
logging_otel["OpenTelemetry"]:::support
logging_data_prepper["Data Prepper"]:::support
logging_oauth_proxy --> logging_dashboards
logging_dashboards --> logging_opensearch
logging_fluent_bit --> logging_opensearch
logging_otel --> logging_data_prepper
logging_data_prepper --> logging_opensearch
end
subgraph longhorn_ns["longhorn-system"]
direction LR
longhorn_oauth_proxy["Longhorn proxy"]:::oauth
longhorn_frontend["Longhorn UI"]:::user
longhorn_backend["Longhorn manager"]:::data
longhorn_webhooks["Admission and recovery"]:::support
longhorn_oauth_proxy --> longhorn_frontend
longhorn_frontend --> longhorn_backend
longhorn_webhooks --> longhorn_backend
end
subgraph monitoring_ns["monitoring"]
direction LR
monitoring_grafana["Grafana"]:::user
monitoring_victoria["VictoriaMetrics"]:::data
monitoring_exporters["Cluster and node exporters"]:::support
monitoring_gpu_exporters["GPU exporters"]:::support
monitoring_quality_gateway["Quality gateway"]:::support
monitoring_postmark["Postmark exporter"]:::support
monitoring_vmalert["vmalert"]:::support
monitoring_alertmanager["Alertmanager"]:::user
monitoring_exporters --> monitoring_victoria
monitoring_gpu_exporters --> monitoring_victoria
monitoring_quality_gateway --> monitoring_victoria
monitoring_postmark --> monitoring_victoria
monitoring_victoria --> monitoring_grafana
monitoring_vmalert --> monitoring_alertmanager
end
end
subgraph security_services["Identity and Security"]
direction TB
subgraph quality_ns["quality"]
direction LR
quality_oauth_proxy["SonarQube proxy"]:::oauth
quality_sonarqube["SonarQube"]:::user
quality_exporter["Quality exporter"]:::support
quality_oauth_proxy --> quality_sonarqube
quality_exporter --> quality_sonarqube
end
subgraph sso_ns["sso"]
direction LR
sso_oauth_proxy["Shared access proxy"]:::oauth
sso_keycloak["Keycloak"]:::user
atlas_realm["atlas realm"]:::control
cassandra_realm["cassandra realm"]:::control
sso_openldap["OpenLDAP"]:::directory
sso_jobs["Realm bootstrap and checks"]:::ephemeral
sso_keycloak --> atlas_realm
sso_keycloak --> cassandra_realm
sso_jobs --> sso_keycloak
end
subgraph vault_ns["vault"]
direction LR
vault_service["Vault"]:::user
vault_internal["Vault cluster service"]:::support
vault_injector["Secret injector"]:::support
vault_jobs["Configuration jobs"]:::ephemeral
vault_service --> vault_internal
vault_injector --> vault_service
vault_jobs --> vault_service
end
end
end
gitops_domain gitops_identity@-->|HTTPS| gitops_oidc_access
gitops_oidc_access gitops_entry@--> flux_weave
gitea_domain gitea_atlas_identity@-->|HTTPS| gitea_atlas_oidc_access
gitea_atlas_oidc_access gitea_atlas_entry@--> gitea_http
gitea_domain gitea_cassandra_identity@-->|HTTPS| gitea_cassandra_oidc_access
gitea_cassandra_oidc_access gitea_cassandra_entry@--> gitea_http
gitea_domain gitea_ssh_entry@-->|SSH| gitea_ssh
harbor_domain harbor_identity@-->|HTTPS| harbor_oidc_access
harbor_oidc_access harbor_entry@--> harbor_portal
harbor_domain harbor_registry_entry@-->|OCI| harbor_registry
hermes_domain hermes_identity@-->|HTTPS| hermes_oidc_access
hermes_oidc_access hermes_entry@--> hermes_agent
jenkins_domain jenkins_identity@-->|HTTPS| jenkins_oidc_access
jenkins_oidc_access jenkins_entry@--> jenkins_service
logs_domain logs_identity@-->|HTTPS| logs_oauth_access
logs_oauth_access logs_entry@--> logging_oauth_proxy
longhorn_domain longhorn_identity@-->|HTTPS| longhorn_oauth_access
longhorn_oauth_access longhorn_entry@--> longhorn_oauth_proxy
grafana_domain grafana_identity@-->|HTTPS| grafana_oidc_access
grafana_oidc_access grafana_entry@--> monitoring_grafana
alerts_domain alerts_entry@-->|HTTPS| monitoring_alertmanager
quality_domain quality_identity@-->|HTTPS| quality_oauth_access
quality_oauth_access quality_entry@--> quality_oauth_proxy
keycloak_domain keycloak_identity@-->|HTTPS| keycloak_admin_access
keycloak_admin_access keycloak_entry@--> sso_keycloak
vault_domain vault_identity@-->|HTTPS| vault_oidc_access
vault_oidc_access vault_entry@--> vault_service
end
subgraph shared_plane["Shared Cluster Services"]
direction LR
subgraph ingress_plane["Ingress and Networking"]
direction TB
metallb_control["MetalLB<br/>Service addresses"]:::control
traefik_service["Traefik<br/>Load balancer"]:::support
traefik_controller["Traefik<br/>TLS and routing"]:::support
cert_manager["cert-manager"]:::support
cert_webhooks["Certificate webhooks"]:::support
metallb_control --> traefik_service
traefik_service --> traefik_controller
cert_webhooks --> cert_manager
cert_manager --> traefik_controller
end
subgraph shared_state["Shared State and Models"]
direction TB
postgres_service["Shared PostgreSQL<br/>Application and platform databases"]:::data
ai_ollama["Ollama<br/>Local model service"]:::support
default_zot_proxy["Zot access proxy"]:::oauth
end
subgraph maintenance_ns["maintenance"]
direction TB
maintenance_ariadne["Ariadne"]:::support
maintenance_metis_proxy["Metis proxy"]:::oauth
maintenance_metis["Metis"]:::support
maintenance_soteria_proxy["Soteria proxy"]:::oauth
maintenance_soteria["Soteria"]:::support
maintenance_node_ops["Node operations"]:::ephemeral
maintenance_jobs["Repair and migration jobs"]:::ephemeral
maintenance_metis_proxy --> maintenance_metis
maintenance_soteria_proxy --> maintenance_soteria
maintenance_ariadne --> maintenance_node_ops
maintenance_ariadne --> maintenance_jobs
end
subgraph telemetry_services["Additional Telemetry"]
direction TB
climate_typhon["Typhon<br/>Tent climate"]:::support
sui_metrics["SUI metrics"]:::support
end
end
subgraph external_systems["Host and Physical Systems"]
direction LR
ananke_hosts["Ananke<br/>titan-db and titan-24"]:::external
pyrphoros["Pyrphoros UPS"]:::external
statera["Statera UPS"]:::external
tent_system["AC Infinity<br/>Controller and cloud"]:::external
ananke_hosts --> pyrphoros
ananke_hosts --> statera
end
firefly_synced_access firefly_directory@-.-> sso_openldap
wger_synced_access wger_directory@-.-> sso_openldap
jellyfin_ldap_access jellyfin_directory@-.-> sso_openldap
website_backend website_model@-.-> ai_ollama
website_backend website_automation@-.-> maintenance_ariadne
hermes_chat hermes_chat_model@-.-> hermes_model_gate
hermes_agent hermes_jenkins@-.-> jenkins_service
hermes_agent hermes_metrics@-.-> monitoring_victoria
hermes_agent hermes_ariadne@-.-> maintenance_ariadne
gitea_http gitea_jenkins@-.->|SCM webhook| jenkins_service
gitea_http gitea_flux@-.->|Flux webhook| flux_webhook
jenkins_agents jenkins_gitea@-.->|Clone and fetch| gitea_http
jenkins_agents jenkins_quality@-.->|Quality scan| quality_sonarqube
jenkins_agents jenkins_harbor@-.->|Images and artifacts| harbor_registry
flux_source flux_gitea@-.->|Desired state| gitea_ssh
flux_image_reflector flux_harbor@-.->|Image metadata| harbor_registry
flux_image_automation flux_commit@-.->|Manifest update| gitea_ssh
flux_kustomize flux_apps@-.-> application_delivery
flux_kustomize flux_personal@-.-> personal_delivery
flux_kustomize flux_platform@-.-> platform_delivery
application_state_clients app_postgres@-.-> postgres_service
personal_state_clients personal_postgres@-.-> postgres_service
platform_state_clients platform_postgres@-.-> postgres_service
cloud_nextcloud nextcloud_mail@-.->|Mail delivery| mail_protocols
quality_exporter quality_metrics@-.-> monitoring_victoria
sui_metrics sui_telemetry@-.-> monitoring_victoria
monitoring_victoria ananke_telemetry@-.-> ananke_hosts
climate_typhon climate_source@--> tent_system
climate_typhon climate_telemetry@-.-> monitoring_victoria
class portal_identity,portal_chat_identity,cassandra_identity,element_identity,matrix_identity,budget_identity,firefly_identity,wolf_identity,wger_identity,chat_identity,jellyfin_identity,cloud_identity,outline_identity,planka_identity,gitops_identity,gitea_atlas_identity,gitea_cassandra_identity,harbor_identity,hermes_identity,jenkins_identity,logs_identity,longhorn_identity,grafana_identity,quality_identity,keycloak_identity,vault_identity routeIngress
class portal_entry,portal_chat_entry,cassandra_entry,element_entry,matrix_entry,budget_entry,firefly_entry,wolf_entry,wger_entry,chat_entry,jellyfin_entry,cloud_entry,outline_entry,planka_entry,gitops_entry,gitea_atlas_entry,gitea_cassandra_entry,harbor_entry,hermes_entry,jenkins_entry,logs_entry,longhorn_entry,grafana_entry,quality_entry,keycloak_entry,vault_entry routeService
class matrix_api,call_entry,livekit_entry,turn_entry,moonlight_entry,pegasus_entry,mail_web_entry,mail_protocol_entry,office_entry,vaultwarden_entry,monero_entry,gitea_ssh_entry,harbor_registry_entry,alerts_entry routeDirect
class firefly_directory,wger_directory,jellyfin_directory routeAuth
class app_postgres,personal_postgres,platform_postgres,nextcloud_mail,climate_source routeData
class quality_metrics,sui_telemetry,ananke_telemetry,climate_telemetry routeTelemetry
class website_automation,hermes_ariadne,gitea_jenkins,gitea_flux,jenkins_gitea,jenkins_quality,jenkins_harbor,flux_gitea,flux_harbor,flux_commit,flux_apps,flux_personal,flux_platform routeControl
class website_model,hermes_chat_model,hermes_jenkins,hermes_metrics routeInternal
end
classDef domain fill:#16324f,stroke:#6cb6ff,color:#ffffff
classDef user fill:#173f2b,stroke:#61d095,color:#ffffff
classDef support fill:#30343b,stroke:#aab2bf,color:#ffffff
classDef data fill:#3b2f52,stroke:#b79cff,color:#ffffff
classDef ephemeral fill:#4a301e,stroke:#f0a35b,color:#ffffff,stroke-dasharray:6 3
classDef control fill:#3b354e,stroke:#c1a7ff,color:#ffffff
classDef access fill:#213a56,stroke:#6cb6ff,color:#ffffff
classDef oauth fill:#3b2f52,stroke:#c1a7ff,color:#ffffff
classDef directory fill:#344a2d,stroke:#8ed081,color:#ffffff
classDef external fill:#44301f,stroke:#ffb86c,color:#ffffff
classDef linkIngress fill:#101820,stroke:#4ea1ff,color:#ffffff
classDef linkService fill:#101820,stroke:#61d095,color:#ffffff
classDef linkDirect fill:#101820,stroke:#e4bd55,color:#ffffff
classDef linkAuth fill:#101820,stroke:#c1a7ff,color:#ffffff
classDef linkData fill:#101820,stroke:#b79cff,color:#ffffff
classDef linkTelemetry fill:#101820,stroke:#55c8d3,color:#ffffff
classDef linkControl fill:#101820,stroke:#f0a35b,color:#ffffff
classDef linkInternal fill:#101820,stroke:#aab2bf,color:#ffffff
classDef routeIngress stroke:#4ea1ff,stroke-width:2.5px,color:#4ea1ff
classDef routeService stroke:#61d095,stroke-width:2.5px,color:#61d095
classDef routeDirect stroke:#e4bd55,stroke-width:2.5px,color:#e4bd55
classDef routeAuth stroke:#c1a7ff,stroke-width:2.2px,color:#c1a7ff,stroke-dasharray:6 4
classDef routeData stroke:#b79cff,stroke-width:2.2px,color:#b79cff,stroke-dasharray:6 4
classDef routeTelemetry stroke:#55c8d3,stroke-width:2.2px,color:#55c8d3,stroke-dasharray:6 4
classDef routeControl stroke:#f0a35b,stroke-width:2.2px,color:#f0a35b,stroke-dasharray:6 4
classDef routeInternal stroke:#aab2bf,stroke-width:2px,color:#aab2bf,stroke-dasharray:5 4
style system fill:#000000,stroke:#8b95a5,stroke-width:2px
style legend fill:#050505,stroke:#6d7485,stroke-width:1.5px
style application_band fill:#02030d,stroke:#55c8d3,stroke-width:2px
style personal_band fill:#06030d,stroke:#8ed081,stroke-width:2px
style platform_band fill:#07040c,stroke:#c1a7ff,stroke-width:2px
style shared_plane fill:#080604,stroke:#e4bd55,stroke-width:2px
style external_systems fill:#080402,stroke:#ffb86c,stroke-width:2px

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.0 MiB

213
mermaid/TestAutomation.mmd Normal file
View File

@ -0,0 +1,213 @@
%% Automatic test failure triage and response.
%% Hermes reads and recommends. Ariadne validates and performs every write.
%% Relationships carry the detail; node text stays concise.
%%{init: {"flowchart": {"defaultRenderer": "elk", "curve": "stepAfter", "nodeSpacing": 32, "rankSpacing": 48, "useMaxWidth": false}, "elk": {"mergeEdges": true, "nodePlacementStrategy": "LINEAR_SEGMENTS", "forceNodeModelOrder": true, "considerModelOrder": "NODES_AND_EDGES"}, "themeVariables": {"background": "#000000"}, "themeCSS": "& { background-color: #000000 !important; }"}}%%
flowchart TD
subgraph system["Hermes Test Failure Triage"]
direction TB
subgraph main_row["Main flow"]
direction TB
subgraph upper_flow["Evidence and analysis"]
direction LR
subgraph reference["Legend"]
direction TB
key_build["Blue<br/>Build and validation"]:::build
key_evidence["Green<br/>Evidence"]:::evidence
key_ariadne["Teal<br/>Ariadne orchestration"]:::ariadne
key_hermes["Purple<br/>Hermes"]:::hermes
key_policy["Gold<br/>Policy and action"]:::policy
key_human["Red<br/>Human required"]:::human
key_limit["Dashed gray<br/>Current boundary"]:::limited
end
subgraph intake["Detect and gather"]
direction TB
detector["Jenkins detector<br/>Allowlisted jobs<br/>Terminal failures only"]:::ariadne
evidence_sources["Evidence sources<br/>Jenkins and OpenSearch<br/>Metrics, Kubernetes, Flux<br/>Gitea and Grafana"]:::evidence
collector["Console reader<br/>Full console<br/>2 MB cap"]:::ariadne
failure_ranker["Failure ranker<br/>Strong evidence first<br/>Tool output alone is neutral"]:::ariadne
context_filter["Context filter<br/>Passing quotes removed<br/>Repeats and overlaps merged"]:::ariadne
bundle["Incident bundle<br/>Failures, logs, metrics<br/>Health, revisions, links"]:::evidence
detector --> evidence_sources
evidence_sources --> collector
collector --> failure_ranker
failure_ranker --> context_filter
context_filter --> bundle
end
subgraph hermes_plane["Hermes analysis"]
direction TB
hermes_access["Read-only access<br/>Jenkins, metrics, Kubernetes<br/>Flux, Gitea, Grafana<br/>Investigation by incident ID"]:::hermes
model_gate["Model gate<br/>openai-codex/gpt-5.6-terra<br/>local gpt-oss:20b fallback"]:::model
skills["Triage skills<br/>Jenkins evidence<br/>Quality and cluster health<br/>Flux and Git correlation"]:::skill
recommendation["Structured recommendation<br/>Classification and confidence<br/>Sourced facts<br/>Action request or patch data"]:::hermes
tool_stream["Agent event stream<br/>Evidence and tool calls"]:::audit
hermes_access --> skills
model_gate -.-> skills
skills --> recommendation
hermes_access -.-> tool_stream
end
end
subgraph lower_flow["Policy and response"]
direction LR
subgraph controls["Ariadne policy gates"]
direction TB
response_check["Response check<br/>Schema, incident, sources<br/>Confidence and action"]:::policy
repair_guard["Scoped repair guard<br/>Classification and action<br/>must match the owning job"]:::policy
marker_check["Transient marker check<br/>DNS, connection, TLS<br/>image pull, upstream 5xx<br/>Disk full is excluded"]:::policy
authorizer["Action authorizer<br/>Allowlist, evidence, confidence<br/>One action, kill switch"]:::policy
response_check -->|scoped repair| repair_guard
repair_guard --> authorizer
response_check -->|transient claim| marker_check
marker_check -->|marker confirmed| authorizer
response_check -->|other result| authorizer
end
subgraph response["Ariadne response"]
direction LR
subgraph response_paths["Response paths"]
direction TB
route{"Policy result"}:::decision
subgraph immediate_paths["Action or escalation"]
direction LR
subgraph operational_path["Allowlisted action"]
direction TB
registry["Action registry"]:::policy
repair_fixture["Scoped ConfigMap repair<br/>In-process patch<br/>No pod created"]:::action
retry_infra["Transient retry<br/>One Jenkins rebuild<br/>No cluster write"]:::action
operational_result["Action result<br/>One attempt<br/>One validation build"]:::action
registry --> repair_fixture
registry --> retry_infra
repair_fixture --> operational_result
retry_infra --> operational_result
end
subgraph escalation_path["Human-required response"]
direction TB
human_required["Diagnosis and next checks<br/>Incident stays human required"]:::human
gitea_issue["Ariadne opens an issue<br/>Evidence and suggested fix"]:::human
human_required --> gitea_issue
end
end
route -->|authorized action| registry
route -->|human required| human_required
operational_result -->|action failed| human_required
end
subgraph code_path["Optional source proposal"]
direction TB
subgraph proposal_stage["Prepare and check"]
direction TB
duplicate_guard["Open proposal check<br/>Existing repair branch"]:::policy
candidate_files["Candidate files<br/>Path and line hints<br/>Read failing test"]:::evidence
source_target["Patch target<br/>Follow test imports<br/>Tests remain read only"]:::policy
patch_proposal["Hermes patch data<br/>Path, anchor, replacement<br/>Reason"]:::hermes
patch_validator["Patch validator<br/>Allowed path and branch<br/>20 lines, 4 KB<br/>Exact anchor"]:::policy
duplicate_guard --> candidate_files
candidate_files --> source_target
source_target --> patch_proposal
patch_proposal --> patch_validator
end
subgraph pr_stage["Deliver for review"]
direction TB
gitea_pr["Ariadne opens a pull request<br/>hermes-repair branch<br/>Ariadne holds the token"]:::git
branch_build["Branch validation<br/>Configured repositories only"]:::limited
human_merge["Human review and merge<br/>No automatic merge"]:::human
flux_delivery["Flux delivery<br/>After human merge"]:::git
gitea_pr -.->|where configured| branch_build
branch_build --> human_merge
gitea_pr -->|otherwise| human_merge
human_merge --> flux_delivery
end
patch_validator -->|validated patch| gitea_pr
end
response_paths -.->|eligible source defect| code_path
end
subgraph outputs["Inspectable outputs"]
direction TB
output_entry["Ariadne records every outcome"]:::audit
audit_events["Audit events<br/>Incident, diagnosis, action<br/>Patch proposal and agent IDs"]:::audit
metrics_output["Triage metrics<br/>VictoriaMetrics and Grafana"]:::metrics
alert_output["Narrow alerts<br/>Failed repair<br/>Escalation untouched for six hours"]:::metrics
issue_output["Gitea issue<br/>Human-required incident<br/>Written by Ariadne"]:::git
pr_output["Gitea pull request<br/>Source proposal<br/>Written by Ariadne"]:::git
output_entry --> audit_events
output_entry --> metrics_output
metrics_output --> alert_output
end
end
reference ~~~ intake
intake ==>|incident bundle| hermes_plane
controls ==>|policy result| response
response ==>|records and artifacts| outputs
upper_flow ==>|recommendation| lower_flow
end
end
classDef build fill:#16324f,stroke:#6cb6ff,color:#ffffff
classDef evidence fill:#173f2b,stroke:#61d095,color:#ffffff
classDef ariadne fill:#24414a,stroke:#69c5d1,color:#ffffff
classDef hermes fill:#3c3155,stroke:#c1a7ff,color:#ffffff
classDef skill fill:#42345b,stroke:#c1a7ff,color:#ffffff
classDef model fill:#342b4f,stroke:#a78bfa,color:#ffffff
classDef policy fill:#3d3525,stroke:#e4bd55,color:#ffffff
classDef decision fill:#47391c,stroke:#e4bd55,color:#ffffff,stroke-width:3px
classDef action fill:#4a3b16,stroke:#e4bd55,color:#ffffff
classDef human fill:#4a2428,stroke:#ff6b78,color:#ffffff
classDef git fill:#253f35,stroke:#78c89a,color:#ffffff
classDef metrics fill:#243750,stroke:#65a9e8,color:#ffffff
classDef audit fill:#353125,stroke:#d9b55b,color:#ffffff
classDef limited fill:#1f2227,stroke:#9ba4b1,color:#c4c9d1,stroke-width:2px,stroke-dasharray:8 6
style system fill:#030303,stroke:#6b7280,stroke-width:3px
style main_row fill:#050607,stroke:#6b7280,stroke-width:2px
style upper_flow fill:#050607,stroke:#61d095,stroke-width:2px
style lower_flow fill:#070605,stroke:#e4bd55,stroke-width:2px
style reference fill:#07090c,stroke:#9ba4b1,stroke-width:2px
style intake fill:#070d09,stroke:#61d095,stroke-width:2px
style hermes_plane fill:#0c0912,stroke:#c1a7ff,stroke-width:3px
style controls fill:#0d0b07,stroke:#e4bd55,stroke-width:2px
style response fill:#090806,stroke:#e4bd55,stroke-width:3px
style response_paths fill:#070707,stroke:#6b7280,stroke-width:1px
style immediate_paths fill:#080706,stroke:#8b8170,stroke-width:1px
style operational_path fill:#0d0b07,stroke:#e4bd55,stroke-width:2px
style escalation_path fill:#0d090a,stroke:#ff6b78,stroke-width:2px
style code_path fill:#0b090d,stroke:#c1a7ff,stroke-width:2px
style proposal_stage fill:#09080b,stroke:#9f86da,stroke-width:1px
style pr_stage fill:#07100a,stroke:#78c89a,stroke-width:1px
style outputs fill:#07100d,stroke:#78c89a,stroke-width:2px

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 575 KiB

View File

@ -1,5 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
python scripts/knowledge_render_atlas.py --write
python scripts/knowledge_render_atlas.py --write --out services/comms/knowledge

View File

@ -1,13 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
# Manual-only helper to run `scripts/test_user_cleanup.py` inside the portal backend container.
# Manual-only helper to run `scripts/manual-tests/test_user_cleanup.py` inside the portal backend container.
#
# Usage (dry-run):
# scripts/test_user_cleanup.sh --prefix test-
# scripts/manual-tests/test_user_cleanup.sh --prefix test-
#
# Usage (apply):
# scripts/test_user_cleanup.sh --prefix test- --apply --confirm test-
# scripts/manual-tests/test_user_cleanup.sh --prefix test- --apply --confirm test-
NS="${PORTAL_NAMESPACE:-bstein-dev-home}"
TARGET="${PORTAL_BACKEND_EXEC_TARGET:-deploy/bstein-dev-home-backend}"

View File

@ -11,10 +11,10 @@ conservative:
- Supports a protected email allowlist to prevent catastrophic mistakes.
Example (dry-run):
scripts/test_vaultwarden_user_cleanup.py --prefix e2e-
scripts/manual-tests/test_vaultwarden_user_cleanup.py --prefix e2e-
Example (apply):
scripts/test_vaultwarden_user_cleanup.py --prefix e2e- --apply --confirm e2e-
scripts/manual-tests/test_vaultwarden_user_cleanup.py --prefix e2e- --apply --confirm e2e-
"""
from __future__ import annotations

View File

@ -4,10 +4,10 @@ set -euo pipefail
# Manual-only helper to clean Vaultwarden test users and invites from Postgres.
#
# Usage (dry-run):
# scripts/test_vaultwarden_user_cleanup.sh --prefix e2e-
# scripts/manual-tests/test_vaultwarden_user_cleanup.sh --prefix e2e-
#
# Usage (apply):
# scripts/test_vaultwarden_user_cleanup.sh --prefix e2e- --apply --confirm e2e-
# scripts/manual-tests/test_vaultwarden_user_cleanup.sh --prefix e2e- --apply --confirm e2e-
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"

View File

@ -26,7 +26,7 @@ while [[ $# -gt 0 ]]; do
;;
-h|--help)
cat <<USAGE
Usage: scripts/build_ananke_node_helper.sh [--image <image>] [--docker-config <path>] [--platforms <csv>] [--builder <name>]
Usage: scripts/ops/build_ananke_node_helper.sh [--image <image>] [--docker-config <path>] [--platforms <csv>] [--builder <name>]
USAGE
exit 0
;;

View File

@ -31,7 +31,7 @@ while [[ $# -gt 0 ]]; do
;;
-h|--help)
cat <<USAGE
Usage: scripts/build_harbor_bootstrap_bundle.sh [--images-file <path>] [--bundle-file <path>] [--docker-config <path>] [--platform <linux/arm64>] [--zstd-level <level>]
Usage: scripts/ops/build_harbor_bootstrap_bundle.sh [--images-file <path>] [--bundle-file <path>] [--docker-config <path>] [--platform <linux/arm64>] [--zstd-level <level>]
USAGE
exit 0
;;

View File

@ -6,7 +6,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
usage() {
cat <<'USAGE'
Usage:
scripts/cluster_power_console.sh [--repo-dir <path>] [--delegate-host <host>] <shutdown|startup> [recovery-script-options...]
scripts/ops/cluster_power_console.sh [--repo-dir <path>] [--delegate-host <host>] <shutdown|startup> [recovery-script-options...]
Purpose:
Friendly manual entrypoint for running Ananke from a remote console.
@ -17,9 +17,9 @@ Defaults:
--delegate-host titan-db
Examples:
scripts/cluster_power_console.sh shutdown --execute
scripts/cluster_power_console.sh startup --execute --force-flux-branch main
scripts/cluster_power_console.sh --delegate-host titan-24 shutdown --execute
scripts/ops/cluster_power_console.sh shutdown --execute
scripts/ops/cluster_power_console.sh startup --execute --force-flux-branch main
scripts/ops/cluster_power_console.sh --delegate-host titan-24 shutdown --execute
USAGE
}
@ -57,7 +57,7 @@ if [[ $# -lt 1 ]]; then
fi
SIBLING_SCRIPT="${SCRIPT_DIR}/cluster_power_recovery.sh"
REPO_SCRIPT="${REPO_DIR}/scripts/cluster_power_recovery.sh"
REPO_SCRIPT="${REPO_DIR}/scripts/ops/cluster_power_recovery.sh"
LOCAL_SCRIPT=""
if [[ -x "${SIBLING_SCRIPT}" ]]; then
@ -82,6 +82,6 @@ remote_cmd=""
if [[ -n "${REMOTE_REPO_DIR}" ]]; then
remote_cmd+="ANANKE_REPO_DIR=$(printf '%q' "${REMOTE_REPO_DIR}") "
fi
remote_cmd+="if [ -x ~/ananke-tools/cluster_power_recovery.sh ]; then ~/ananke-tools/cluster_power_recovery.sh ${quoted_args}; elif [ -x ${quoted_repo_dir}/scripts/cluster_power_recovery.sh ]; then ${quoted_repo_dir}/scripts/cluster_power_recovery.sh ${quoted_args}; else echo 'cluster-power-console: remote recovery script not found' >&2; exit 1; fi"
remote_cmd+="if [ -x ~/ananke-tools/cluster_power_recovery.sh ]; then ~/ananke-tools/cluster_power_recovery.sh ${quoted_args}; elif [ -x ${quoted_repo_dir}/scripts/ops/cluster_power_recovery.sh ]; then ${quoted_repo_dir}/scripts/ops/cluster_power_recovery.sh ${quoted_args}; elif [ -x ${quoted_repo_dir}/scripts/cluster_power_recovery.sh ]; then ${quoted_repo_dir}/scripts/cluster_power_recovery.sh ${quoted_args}; else echo 'cluster-power-console: remote recovery script not found' >&2; exit 1; fi"
exec ssh -o BatchMode=yes -o ConnectTimeout=8 "${DELEGATE_HOST}" "${remote_cmd}"

View File

@ -16,7 +16,7 @@ fi
usage() {
cat <<USAGE
Usage:
scripts/cluster_power_recovery.sh <prepare|status|bootstrap-seed|harbor-seed|longhorn-seed|longhorn-unlock|flux-hold|shutdown|startup> [options]
scripts/ops/cluster_power_recovery.sh <prepare|status|bootstrap-seed|harbor-seed|longhorn-seed|longhorn-unlock|flux-hold|shutdown|startup> [options]
Options:
--execute Actually run commands (default is dry-run)
@ -77,14 +77,14 @@ Options:
-h, --help Show help
Examples:
scripts/cluster_power_recovery.sh prepare --execute
scripts/cluster_power_recovery.sh bootstrap-seed --execute
scripts/cluster_power_recovery.sh harbor-seed --execute
scripts/cluster_power_recovery.sh longhorn-unlock --execute
scripts/cluster_power_recovery.sh flux-hold --execute
scripts/cluster_power_recovery.sh status
scripts/cluster_power_recovery.sh shutdown --execute
scripts/cluster_power_recovery.sh startup --execute --force-flux-branch main
scripts/ops/cluster_power_recovery.sh prepare --execute
scripts/ops/cluster_power_recovery.sh bootstrap-seed --execute
scripts/ops/cluster_power_recovery.sh harbor-seed --execute
scripts/ops/cluster_power_recovery.sh longhorn-unlock --execute
scripts/ops/cluster_power_recovery.sh flux-hold --execute
scripts/ops/cluster_power_recovery.sh status
scripts/ops/cluster_power_recovery.sh shutdown --execute
scripts/ops/cluster_power_recovery.sh startup --execute --force-flux-branch main
USAGE
}

View File

@ -1,3 +1,5 @@
#!/usr/bin/env fish
### ------- helpers ---------------------------------------------------------
function _need --description "ensure a command exists"

View File

@ -1,3 +1,5 @@
#!/usr/bin/env fish
### --------- helpers ----------
function _need --description "ensure a command exists"
for c in $argv

View File

@ -0,0 +1,15 @@
# Copy to hermes_triage_demo.env (same directory) and fill in.
# That filename is git-ignored so it can hold real tokens.
# Jenkins API token: https://ci.bstein.dev -> your user -> Configure -> API Token
export JENKINS_USER="your-jenkins-user"
export JENKINS_TOKEN="your-jenkins-api-token"
# Gitea token, used only to report open pull requests during preflight.
# Preflight still works without it; that one line will read "?".
export GITEA_TOKEN="your-gitea-token"
# Override only if you are not pointing at the usual lab.
# export JENKINS_URL="https://ci.bstein.dev"
# export GITEA_URL="https://scm.bstein.dev"
# export CODE_REPO_DIR="$HOME/Development/hermes-code-demo"

294
scripts/ops/hermes_triage_demo.sh Executable file
View File

@ -0,0 +1,294 @@
#!/usr/bin/env bash
# Drive and narrate the Hermes automated-triage demos.
#
# hermes_triage_demo.sh fixture # autonomous loop: fail -> repair -> green
# hermes_triage_demo.sh code # proposal loop: fail -> Hermes patch -> PR
# hermes_triage_demo.sh status # current incident/alert state, no changes
# hermes_triage_demo.sh preflight # confirm the lab is ready to demo
# hermes_triage_demo.sh reset # restore the demo to its pre-run state
# hermes_triage_demo.sh monitor [code] # stream the flow chart stages live
#
# FIRST RUN: copy hermes_triage_demo.env.example to hermes_triage_demo.env in
# this directory and fill it in. That file is git-ignored precisely so it can
# hold real tokens; this script sources it automatically, so nothing needs to
# be exported by hand.
#
# Needs kubectl access to the cluster as well. Nothing here mutates the cluster
# directly: the fixture demo only asks Jenkins to run a parameterized build,
# and the code demo only pushes a seeded defect to the demo repository.
set -euo pipefail
# Local, git-ignored credentials. Sourced before anything else so every value
# below can be overridden from it.
_DEMO_ENV="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/hermes_triage_demo.env"
# shellcheck disable=SC1090
[ -r "$_DEMO_ENV" ] && . "$_DEMO_ENV"
JENKINS_URL="${JENKINS_URL:-https://ci.bstein.dev}"
FIXTURE_JOB="hermes-triage-demo"
CODE_JOB="hermes-code-demo"
DEMO_NS="hermes-triage-demo"
CODE_REPO_DIR="${CODE_REPO_DIR:-$HOME/Development/hermes-code-demo}"
say() { printf '\n\033[1m[%s] %s\033[0m\n' "$(date -u +%H:%M:%S)" "$*"; }
note() { printf ' %s\n' "$*"; }
require_jenkins() {
if [ -z "${JENKINS_USER:-}" ] || [ -z "${JENKINS_TOKEN:-}" ]; then
echo "Missing Jenkins credentials." >&2
echo "Create $_DEMO_ENV from hermes_triage_demo.env.example and fill it in." >&2
exit 1
fi
}
jenkins_get() { curl -sk -u "$JENKINS_USER:$JENKINS_TOKEN" "$JENKINS_URL$1"; }
jenkins_post() { curl -sk -o /dev/null -w '%{http_code}' -u "$JENKINS_USER:$JENKINS_TOKEN" -X POST "$JENKINS_URL$1"; }
last_build_number() {
jenkins_get "/job/$1/api/json?tree=lastBuild[number]" |
python3 -c 'import json,sys; print(json.load(sys.stdin)["lastBuild"]["number"])'
}
wait_for_build() { # job number -> prints result
local job="$1" num="$2" tries="${3:-120}"
for _ in $(seq 1 "$tries"); do
sleep 10
local body result building
body="$(jenkins_get "/job/$job/$num/api/json?tree=result,building" || true)"
building="$(printf '%s' "$body" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("building"))' 2>/dev/null || echo unknown)"
if [ "$building" = "False" ]; then
result="$(printf '%s' "$body" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("result"))')"
printf '%s' "$result"
return 0
fi
done
printf 'TIMEOUT'
}
ariadne_ticks() { # tail the autotriage decisions in human-readable form
kubectl -n maintenance logs deploy/ariadne -c ariadne --tail="${1:-400}" 2>/dev/null |
grep 'hermes autotriage tick' |
python3 -c '
import sys, json
for line in sys.stdin:
try:
d = json.loads(line)
except ValueError:
continue
print(" ", d["timestamp"][11:19], d.get("jobs"))' | tail -"${2:-5}"
}
# `monitor` follows the fixture job; `monitor code` follows the code-proposal
# job, which takes the source-proposal branch of the chart instead.
cmd_monitor() {
local which="${1:-fixture}"
[ "$which" = "code" ] && export MONITOR_JOB="$CODE_JOB"
exec python3 "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/hermes_triage_monitor.py"
}
# Deletes the demo repositories' issues, pull requests and repair branches so a
# rerun starts from nothing. DEMO_REPOS is the whole blast radius and is
# deliberately explicit: a real service's issues are genuine triage records,
# and clearing them to tidy a demo would destroy the evidence the system
# exists to produce.
DEMO_REPOS="${DEMO_REPOS:-hermes-code-demo}"
cmd_reset() {
require_jenkins
say "Reset — restoring the demo to its pre-run state"
note "fixture -> healthy"
if kubectl -n "$DEMO_NS" patch cm hermes-triage-demo-fixture \
--type merge -p '{"data":{"state":"healthy"}}' >/dev/null 2>&1; then
note " fixture: $(kubectl -n "$DEMO_NS" get cm hermes-triage-demo-fixture -o jsonpath='{.data.state}')"
else
note " fixture patch failed (is the demo namespace present?)"
fi
local gitea="${GITEA_URL:-https://scm.bstein.dev}"
if [ -z "${GITEA_TOKEN:-}" ]; then
note "GITEA_TOKEN unset; skipping repository cleanup"
else
for repo in $DEMO_REPOS; do
note "clearing bstein/$repo (demo repository)"
local items
items="$(curl -s --max-time 25 -H "Authorization: token $GITEA_TOKEN" \
"$gitea/api/v1/repos/bstein/$repo/issues?state=all&limit=100" |
python3 -c 'import json,sys
for i in json.load(sys.stdin):
print(i["number"], "pr" if i.get("pull_request") else "issue")' 2>/dev/null || true)"
if [ -z "$items" ]; then
note " no issues or pull requests"
else
while read -r num kind; do
[ -z "$num" ] && continue
curl -s --max-time 25 -o /dev/null -X DELETE -H "Authorization: token $GITEA_TOKEN" \
"$gitea/api/v1/repos/bstein/$repo/issues/$num"
note " deleted $kind #$num"
done <<< "$items"
fi
local branches
branches="$(curl -s --max-time 25 -H "Authorization: token $GITEA_TOKEN" \
"$gitea/api/v1/repos/bstein/$repo/branches" |
python3 -c 'import json,sys,urllib.parse
for b in json.load(sys.stdin):
if b["name"].startswith("hermes-repair/"):
print(urllib.parse.quote(b["name"], safe=""))' 2>/dev/null || true)"
if [ -z "$branches" ]; then
note " no repair branches"
else
for ref in $branches; do
curl -s --max-time 25 -o /dev/null -X DELETE -H "Authorization: token $GITEA_TOKEN" \
"$gitea/api/v1/repos/bstein/$repo/branches/$ref"
note " deleted branch $(printf '%b' "${ref//%/\\x}")"
done
fi
done
fi
if [ -d "$CODE_REPO_DIR/.git" ]; then
note "restoring the demo repository working state"
( cd "$CODE_REPO_DIR" && git checkout -q master && git pull -q --ff-only 2>/dev/null || true )
if grep -q 'percent / 100' "$CODE_REPO_DIR/src/discount.py" 2>/dev/null; then
note " src/discount.py is correct; demo is armable"
else
note " src/discount.py still carries the seeded defect — revert it before demoing"
fi
else
note "demo repository not cloned at $CODE_REPO_DIR; skipping"
fi
say "Ready"
note "real service repositories were not touched"
note "run 'preflight' next, then arm the fixture build"
}
cmd_preflight() {
require_jenkins
say "Preflight"
note "fixture state: $(kubectl -n "$DEMO_NS" get cm hermes-triage-demo-fixture -o jsonpath='{.data.state}' 2>/dev/null || echo MISSING)"
note "ariadne image: $(kubectl -n maintenance get deploy ariadne -o jsonpath='{.spec.template.spec.containers[0].image}')"
note "autoremediation: $(kubectl -n maintenance exec deploy/ariadne -c ariadne -- printenv ARIADNE_HERMES_AUTOREMEDIATION_ENABLED 2>/dev/null)"
# Printed as a list rather than the raw comma-separated setting: this is the
# outermost safety boundary, so it is worth being able to read at a glance.
local allowlist count
allowlist="$(kubectl -n maintenance exec deploy/ariadne -c ariadne -- printenv ARIADNE_HERMES_AUTOTRIAGE_JOB_ALLOWLIST 2>/dev/null | tr ',' ' ')"
count=0
for _job in $allowlist; do count=$((count + 1)); done
note "jobs Ariadne may triage ($count):"
for _job in $allowlist; do note " - $_job"; done
note "hermes: $(kubectl -n hermes get pods -l app=hermes --no-headers | awk '{print $2, $3}')"
local queued
queued="$(jenkins_get '/queue/api/json' | python3 -c 'import json,sys; print(len(json.load(sys.stdin)["items"]))')"
note "jenkins queue depth: $queued (demo is fastest when this is 0)"
# The Kubernetes cloud caps concurrent agent pods at containerCapStr. When
# real CI saturates that cap the demo build sits in the queue reporting
# "all nodes are offline" and the timings in the runbook do not apply.
local agents cap
cap="$(kubectl -n jenkins get cm jenkins-jcasc -o jsonpath='{.data.jenkins\.yaml}' 2>/dev/null |
grep -o 'containerCapStr: "[0-9]*"' | head -1 | grep -o '[0-9]*' || echo 5)"
agents="$(kubectl -n jenkins get pods --no-headers 2>/dev/null | grep -cE '\-[a-z0-9]{5}-[a-z0-9]{5}-[a-z0-9]{5}' || true)"
note "jenkins agent pods: ${agents:-0}/${cap:-5} (a full pool stalls the demo — wait for a free slot)"
local open_prs
open_prs="$(curl -s --max-time 25 -H "Authorization: token ${GITEA_TOKEN:-}" \
"${GITEA_URL:-https://scm.bstein.dev}/api/v1/repos/bstein/hermes-code-demo/pulls?state=open" 2>/dev/null |
python3 -c 'import json,sys; print(len(json.load(sys.stdin)))' 2>/dev/null || echo '?')"
note "open hermes-code-demo PRs: $open_prs (must be 0 — the duplicate guard refuses while one is open)"
}
cmd_status() {
say "Incident state (last ticks)"
ariadne_ticks 600 8
say "Firing alerts"
kubectl -n monitoring exec deploy/vmalert-atlas-availability -- wget -qO- localhost:8880/api/v1/alerts 2>/dev/null |
python3 -c '
import json,sys
alerts = json.load(sys.stdin).get("data", {}).get("alerts", [])
print(" none" if not alerts else "")
for a in alerts:
print(" ", a["name"], a["state"], "build", a.get("labels", {}).get("build"))' 2>/dev/null ||
note "(query vmalert directly if this fails)"
say "Demo namespace"
kubectl -n "$DEMO_NS" get jobs --no-headers 2>/dev/null | sed 's/^/ /'
}
cmd_fixture() {
require_jenkins
local start_num next_num
start_num="$(last_build_number "$FIXTURE_JOB")"
next_num=$((start_num + 1))
say "Arming the demo failure (SEED_FAILURE=true) -> build #$next_num"
note "HTTP $(jenkins_post "/job/$FIXTURE_JOB/buildWithParameters?SEED_FAILURE=true")"
note "Only manual step. Everything after this is automatic."
say "Waiting for the seeded build to fail"
note "result: $(wait_for_build "$FIXTURE_JOB" "$next_num")"
say "Ariadne detects, gathers evidence, asks Hermes, authorizes, repairs"
note "the repair is a single in-process ConfigMap patch, so watch the fixture"
for _ in $(seq 1 40); do
sleep 10
if [ "$(kubectl -n "$DEMO_NS" get cm hermes-triage-demo-fixture -o jsonpath='{.data.state}' 2>/dev/null)" = "healthy" ]; then
note "fixture patched back to healthy"
break
fi
done
ariadne_ticks 400 4
say "Ariadne triggers one rebuild with seeding disabled"
note "result: $(wait_for_build "$FIXTURE_JOB" $((next_num + 1)))"
say "Resolution"
sleep 45
ariadne_ticks 200 3
note "fixture state: $(kubectl -n "$DEMO_NS" get cm hermes-triage-demo-fixture -o jsonpath='{.data.state}')"
}
cmd_code() {
require_jenkins
[ -d "$CODE_REPO_DIR/.git" ] || { echo "clone bstein/hermes-code-demo to $CODE_REPO_DIR first" >&2; exit 1; }
local start_num next_num
start_num="$(last_build_number "$CODE_JOB")"
next_num=$((start_num + 1))
say "Seeding a one-line defect in src/discount.py"
( cd "$CODE_REPO_DIR" && git checkout -q master && git pull -q &&
python3 - <<'PY'
import pathlib
p = pathlib.Path("src/discount.py")
s = p.read_text()
old, new = "percent / 100", "percent / 10"
if old not in s:
raise SystemExit("defect already present or file changed; reset master first")
p.write_text(s.replace(old, new))
PY
git commit -qam "refactor: simplify discount percentage math" && git push -q origin master )
note "pushed: a plausible-looking change that breaks three regression tests"
say "Running the test gate -> build #$next_num"
note "HTTP $(jenkins_post "/job/$CODE_JOB/build")"
note "result: $(wait_for_build "$CODE_JOB" "$next_num")"
say "Ariadne collects evidence and asks Hermes for a minimal patch"
note "Hermes returns an anchored patch as data; Ariadne validates path, size,"
note "changed lines, and that the anchor is unique, then pushes hermes-repair/$next_num"
for _ in $(seq 1 40); do
sleep 15
ariadne_ticks 300 1 | grep -q "code_fix_proposed" && break
done
ariadne_ticks 400 3
say "Pull request awaiting human review (nothing merges automatically)"
note "https://scm.bstein.dev/bstein/hermes-code-demo/pulls"
}
case "${1:-}" in
fixture) cmd_fixture ;;
code) cmd_code ;;
status) cmd_status ;;
preflight) cmd_preflight ;;
reset) cmd_reset ;;
monitor) shift; cmd_monitor "$@" ;;
*) sed -n '2,12p' "$0" | sed 's/^# \{0,1\}//' ; exit 1 ;;
esac

View File

@ -0,0 +1,463 @@
#!/usr/bin/env python3
"""Narrate the triage flow live, showing every command it runs.
Run in a second terminal beside the demo. When a stage of
`mermaid/TestAutomation.mmd` is reached this prints, in order:
the stage banner, naming the chart subgraph
the service UI to look at, if one changes at that stage
each command, echoed before it runs, then its output
Echoing the commands is the point. An audience watching a dashboard has to
take the result on trust; watching `kubectl` run against the cluster and
reading the raw answer is the difference between a demonstration and an
assertion.
Two honest limits. This reports at subgraph granularity, not per node: the
chart draws the collector, response check, marker check and authorizer
separately, and a refusal here names the gate rather than walking all twelve.
And it follows one incident on one job, whereas the chart describes the whole
system.
Read-only. Every command below is a read; nothing here changes the cluster.
"""
from __future__ import annotations
import json
import os
import subprocess
import sys
import time
from datetime import datetime, timezone
JOB = os.environ.get("MONITOR_JOB", "hermes-triage-demo")
NS_DEMO = "hermes-triage-demo"
NS_ARIADNE = "maintenance"
JENKINS = os.environ.get("JENKINS_URL", "https://ci.bstein.dev")
GITEA = os.environ.get("GITEA_URL", "https://scm.bstein.dev")
GRAFANA = os.environ.get("GRAFANA_URL", "https://metrics.bstein.dev")
HERMES_UI = os.environ.get("HERMES_URL", "https://agent.bstein.dev")
POLL_SECONDS = 6
# The fixture job repairs a ConfigMap; the code job proposes a patch. Evidence
# that suits one is false for the other, so the stages branch on it.
IS_CODE_JOB = JOB == "hermes-code-demo"
BOLD, DIM, GREEN, CYAN, YELLOW, RED, RESET = (
"\033[1m", "\033[2m", "\033[32m", "\033[36m", "\033[33m", "\033[31m", "\033[0m"
)
# stage key -> (chart subgraph, what it means, chart path, UI worth showing)
STAGES: dict[str, tuple[str, str, str, str]] = {
"detect": (
"Detect and gather",
"A failed build becomes one incident",
"Jenkins detector -> Evidence sources",
f"{JENKINS}/job/{JOB}/ — the red build",
),
"evidence": (
"Detect and gather",
"Ariadne assembles the bounded evidence bundle",
"Console reader -> Failure ranker -> Context filter -> Incident bundle",
"",
),
"hermes": (
"Hermes analysis",
"Hermes returns a recommendation it cannot act on",
"Triage skills -> Structured recommendation",
f"{HERMES_UI} — the agent run appears here",
),
"gates": (
"Ariadne policy gates",
"Ariadne decides on its own reading of the evidence",
"Response check -> Scoped repair guard -> Action authorizer",
"",
),
"route": (
"Ariadne response",
"The policy result, and which branch it opens",
"Policy result -> Action registry",
"",
),
"response": (
"Ariadne response",
"Ariadne executes, proposes, or escalates",
"Action registry -> Scoped ConfigMap repair -> Action result",
"",
),
"verify": (
"Ariadne response",
"The branch build checks the proposal" if JOB == "hermes-code-demo"
else "One rebuild decides whether the repair held",
"Action result -> validation build",
f"{JENKINS}/job/{JOB}/ — a new build starts on its own",
),
"outputs": (
"Inspectable outputs",
"Incident closed; the artifacts remain",
"Ariadne records every outcome -> Audit events, Triage metrics",
f"{GRAFANA}/d/atlas-testing — triage panels",
),
}
ORDER = ["detect", "evidence", "hermes", "gates", "route", "response", "verify", "outputs"]
def stamp() -> str:
return datetime.now(timezone.utc).strftime("%H:%M:%S")
def run(cmd: list[str], *, show: bool = True, limit: int = 12) -> str:
"""Echo a command, run it, print its output, and return stdout."""
if show:
print(f" {DIM}${RESET} {CYAN}{' '.join(cmd)}{RESET}")
try:
# Ariadne emits ~200 lines per tick, so a ten-minute window is a
# few thousand lines; the fetch needs room to finish.
done = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
out = done.stdout.strip()
except Exception as exc: # a failed read must never stop the narration
print(f" {RED}(command failed: {exc}){RESET}")
return ""
if show:
for line in (out.split("\n")[:limit] or ["(no output)"]):
print(f" {line[:150]}")
print()
return out
def quiet(cmd: list[str]) -> str:
return run(cmd, show=False)
def banner(key: str) -> None:
subgraph, meaning, path, ui = STAGES[key]
print(f"\n{GREEN}{'' * 70}{RESET}")
print(f"{GREEN}{BOLD} {stamp()} {subgraph}{RESET}{GREEN}{meaning}{RESET}")
print(f"{DIM} chart: {path}{RESET}")
if ui:
print(f"{YELLOW} look at: {ui}{RESET}")
print(f"{GREEN}{'' * 70}{RESET}")
def ariadne_records(since: str = "10m") -> list[dict]:
"""Return recent Ariadne log records that parse as JSON.
Windowed by time, not line count. Ariadne emits roughly two hundred lines
per tick once it is polling every job, so a --tail window wide enough to
be useful is impossible to guess and a narrow one silently drops ticks.
"""
raw = quiet(
["kubectl", "-n", NS_ARIADNE, "logs", "deploy/ariadne", "-c", "ariadne", f"--since={since}"]
)
records = []
for line in raw.split("\n"):
line = line.strip()
if line.startswith("{"):
try:
records.append(json.loads(line))
except ValueError:
continue
return records
def job_states(records: list[dict]) -> list[dict]:
"""Return every tick result for this job in the window, oldest first.
The stages are reconstructed from all of them rather than from the newest
alone. A tick reporting the repair is replaced by the next tick within
seconds, so reading only the latest state means a poll landing at the
wrong moment loses that step permanently.
"""
states = []
for record in records:
if record.get("event") == "hermes_autotriage" and record.get("jobs"):
try:
state = json.loads(record["jobs"]).get(JOB)
except ValueError:
continue
if state:
states.append(state)
return states
_DIAG_QUERY = (
". /vault/secrets/ariadne-env.sh >/dev/null 2>&1; python3 -c \""
"import json,os,psycopg;"
"conn=psycopg.connect(os.environ['ARIADNE_DATABASE_URL']);"
"cur=conn.cursor();"
"cur.execute(\\\"select detail from ariadne_events where event_type='hermes_autotriage_diagnosis'"
" order by id desc limit 1\\\");"
"r=cur.fetchone()[0];"
"d=r if isinstance(r,dict) else json.loads(r);"
"print(json.dumps({'incident':d.get('incident_id'),'authorized':d.get('authorized'),"
"'authorize_reason':d.get('authorize_reason'),'evidence_marker':d.get('evidence_marker'),"
"'run_id':(d.get('run') or {}).get('run_id'),"
"'run_seconds':(d.get('run') or {}).get('duration_seconds'),"
"'outcome':d.get('outcome')},indent=1))\""
)
_HISTORY_QUERY = (
". /vault/secrets/ariadne-env.sh >/dev/null 2>&1; python3 -c \""
"import json,os,psycopg;"
"conn=psycopg.connect(os.environ['ARIADNE_DATABASE_URL']);"
"cur=conn.cursor();"
"cur.execute(\\\"select created_at,detail from ariadne_events where"
" event_type='hermes_autotriage_incident' order by id desc limit 40\\\");"
"rows=[(t,d if isinstance(d,dict) else json.loads(d)) for t,d in cur.fetchall()];"
"rows=[r for r in rows if r[1].get('incident_id')=='__INCIDENT__'];"
"[print(str(t)[11:19], v.get('status'), json.dumps(v.get('phase') or {})[:80])"
" for t,v in reversed(rows)]\""
)
_BUNDLE_QUERY = (
"python3 -c \""
"from ariadne.services import hermes_autotriage_evidence as ev;"
"lb={'number':__BUILD__,'result':'FAILURE','building':False,'timestamp':0,'duration':0,'url':''};"
"b=ev.collect_evidence('__INCIDENT__','__JOB__',lb);"
"j=b['jenkins']; regions=j.get('console_failures') or []; tests=j.get('failed_tests') or [];"
"recs=(b.get('log_evidence') or {}).get('records') or [];"
"print('jenkins.console_failures : %d region(s), truncated=%s' % (len(regions), j.get('console_truncated')));"
"[print(' | ' + l[:110]) for l in ((regions[0].get('text') or '').strip().split(chr(10))[-3:] if regions else [])];"
"print('jenkins.failed_tests : %d' % len(tests));"
"[print(' | %s :: %s' % (t.get('className'), t.get('name'))) for t in tests[:2]];"
"print('log_evidence.records : %d from OpenSearch kube-*' % len(recs));"
"[print(' | [%s] %s' % (r.get('namespace'), (r.get('message') or '')[:90])) for r in recs[:2]]\""
)
def bundle_sample(incident: str) -> None:
"""Show a trimmed sample of the bundle that was sent to Hermes.
Rebuilt from the same collector Ariadne used. A finished build's console
does not change, so this is durable rather than a live reading, and it is
trimmed hard on purpose: the point is to show what kind of evidence each
source contributes, not to reprint the bundle.
"""
if "/" not in incident:
return
job, _, build = incident.rpartition("/")
query = (
_BUNDLE_QUERY.replace("__BUILD__", build)
.replace("__INCIDENT__", incident)
.replace("__JOB__", job)
)
run(["kubectl", "-n", NS_ARIADNE, "exec", "deploy/ariadne", "-c", "ariadne", "--",
"sh", "-c", ". /vault/secrets/ariadne-env.sh >/dev/null 2>&1; " + query], limit=14)
def incident_history(incident: str) -> None:
"""Show this incident's recorded state changes, oldest first.
Durable evidence on purpose. A stage describes a moment that has passed,
so reading live cluster state at that point misrepresents it: by the time
the detection stage is narrated the repair has already run, and the
fixture would read healthy as though it had never failed.
"""
if not incident:
return
run(["kubectl", "-n", NS_ARIADNE, "exec", "deploy/ariadne", "-c", "ariadne", "--",
"sh", "-c", _HISTORY_QUERY.replace("__INCIDENT__", incident)], limit=10)
def diagnosis_event() -> None:
"""Show the diagnosis Ariadne stored: what Hermes said, and the verdict."""
run(["kubectl", "-n", NS_ARIADNE, "exec", "deploy/ariadne", "-c", "ariadne", "--",
"sh", "-c", _DIAG_QUERY], limit=16)
def evidence_for(key: str, incident: str = "") -> None:
"""Run the reads that show this stage actually happened."""
if key == "detect":
print(f" {DIM}the incident's recorded state changes; durable, so it still reads"
f" true after the repair has run:{RESET}")
incident_history(incident)
elif key == "evidence":
print(f" {DIM}a sample of what each source contributed to the bundle Hermes"
f" received:{RESET}")
bundle_sample(incident)
elif key == "hermes":
print(f" {DIM}what Hermes actually returned, as Ariadne stored it. The run_id below"
f" identifies this run in the Hermes dashboard, where the prompt it was given"
f" and every tool call it made are visible:{RESET}")
diagnosis_event()
print(f" {DIM}Hermes holds no Git or Kubernetes write access; this JSON is its"
f" entire output{RESET}\n")
elif key == "gates":
print(f" {DIM}the authorization is a match between three separate things: an action"
f" Ariadne already has code to perform, an action id Hermes is permitted to"
f" request, and the action Hermes actually recommended. Ariadne holds the"
f" registry below; Hermes cannot add to it, and the recommendation only"
f" proceeds because it names something already in it.{RESET}")
run(["kubectl", "-n", NS_ARIADNE, "exec", "deploy/ariadne", "-c", "ariadne", "--",
"printenv", "ARIADNE_HERMES_ALLOWED_ACTIONS"], limit=2)
run(["kubectl", "-n", NS_ARIADNE, "exec", "deploy/ariadne", "-c", "ariadne", "--",
"printenv", "ARIADNE_HERMES_MIN_CONFIDENCE"], limit=2)
print(f" {DIM}a separate setting governs what Hermes may be asked to propose a fix"
f" for. It is separate because these become pull requests rather than"
f" changes Ariadne makes itself:{RESET}")
run(["kubectl", "-n", NS_ARIADNE, "exec", "deploy/ariadne", "-c", "ariadne", "--",
"printenv", "ARIADNE_HERMES_FIX_CATEGORIES"], limit=2)
print()
elif key == "route":
if IS_CODE_JOB:
print(f" {DIM}a source fix is not one of the registered actions, so this takes"
f" the Optional source proposal branch rather than the Action registry.{RESET}\n")
else:
print(f" {DIM}the fixture repair is an operational action, so the Optional source"
f" proposal branch is not taken for this incident{RESET}\n")
elif key in {"response", "outputs"}:
if IS_CODE_JOB:
print(f" {DIM}the pull request Ariadne opened is recorded on the incident above"
f" (branch, pr_number, url). Hermes produced the patch as data; Ariadne"
f" validated it and pushed the branch.{RESET}")
print(f" {YELLOW}pull requests: {GITEA}/bstein/hermes-code-demo/pulls{RESET}\n")
else:
run(["kubectl", "-n", NS_DEMO, "get", "cm", "hermes-triage-demo-fixture",
"-o", "jsonpath={.data.state}"])
if key == "response":
print(f" {DIM}that value read 'unhealthy' when the build failed - the seeded"
f" fault - and reads 'healthy' above because Ariadne has just patched"
f" it. That single field changing is the repair.{RESET}\n")
if key == "outputs":
print(f" {DIM}on the chart this is the 'records and artifacts' edge out of the"
f" whole Ariadne response box, not out of one branch. Every path ends"
f" here: an executed action, an escalation, or a pull request all record"
f" the same audit events and metrics.{RESET}")
print(f" {YELLOW}issues filed by triage: {GITEA}/bstein/ariadne/issues{RESET}\n")
elif key == "verify":
if IS_CODE_JOB:
print(f" {DIM}the branch build validates the proposal. Nothing merges"
f" automatically: the incident stays human-required whatever the branch"
f" build says, because a person decides whether the fix is right.{RESET}\n")
else:
print(f" {DIM}exactly one rebuild is triggered; it never retries in a loop. This"
f" completes the operational branch: because the action was authorized and"
f" performed, neither the human-required response nor the optional source"
f" proposal is entered for this incident.{RESET}\n")
class Monitor:
"""Track which flow-chart stage the current incident has reached."""
def __init__(self) -> None:
self.done: set[str] = set()
self.incident = ""
self.summarised = False
self.last_tick = ""
def mark(self, key: str, evidence: str) -> None:
if key in self.done:
return
self.done.add(key)
banner(key)
print(f" {BOLD}what happened:{RESET} {evidence}\n")
evidence_for(key, self.incident)
def new_incident(self, incident: str) -> None:
if incident and incident != self.incident:
self.incident = incident
self.done.clear()
self.summarised = False
print(f"\n{BOLD}{YELLOW}══ incident {incident} ══{RESET}")
def checklist(self) -> None:
print(f"\n{BOLD} flow chart progress{RESET}")
for key in ORDER:
tick = f"{GREEN}{RESET}" if key in self.done else f"{DIM}·{RESET}"
print(f" {tick} {key:<9} {STAGES[key][0]:<22} {DIM}{STAGES[key][2]}{RESET}")
print()
def main() -> None:
print(f"{BOLD}Hermes triage monitor — following mermaid/TestAutomation.mmd{RESET}")
print(f"{DIM}Every command is echoed before it runs. Read-only. Ctrl-C to stop.{RESET}")
print(f"{YELLOW}Jenkins: {JENKINS}/job/{JOB}/{RESET}")
print(f"{YELLOW}Gitea: {GITEA}/bstein{RESET}")
print(f"{YELLOW}Grafana: {GRAFANA}/d/atlas-testing{RESET}\n")
monitor = Monitor()
while True:
states = job_states(ariadne_records())
# Replay every tick in the window, so a step that lasted one tick is
# never lost to poll timing. mark() is idempotent.
for state in states:
status = str(state.get("status") or "")
monitor.new_incident(str(state.get("incident_id") or ""))
if status in {"human_required", "awaiting_rebuild", "deduped", "failed"}:
monitor.mark("detect", f"incident {monitor.incident} opened from a terminal failure")
if status == "awaiting_rebuild":
monitor.mark("evidence", "bundle collected: console regions, tests, logs")
monitor.mark("hermes", "diagnosis returned and parsed against the frozen schema")
monitor.mark("gates", "every gate passed; the predefined action was authorized")
monitor.mark(
"route",
"policy result: authorized -> Action registry (Optional source proposal not taken)",
)
monitor.mark(
"response",
f"Scoped ConfigMap repair: {state.get('repair', 'action')} on {state.get('target', '')}",
)
monitor.mark("verify", "one rebuild triggered with seeding disabled")
if status == "human_required":
reason = str(state.get("reason") or "")
proposed = reason == "code_fix_proposed"
monitor.mark("evidence", "bundle collected")
monitor.mark(
"hermes",
"patch proposed as data: a path, an exact anchor and a replacement"
if proposed
else "diagnosis returned",
)
monitor.mark(
"gates",
"no action was authorized; a source fix is not an action, so this takes"
" the proposal branch rather than the registry"
if proposed
else f"Ariadne refused: {reason} - nothing ran",
)
monitor.mark(
"route",
"policy result: no action -> Optional source proposal -> Patch validator"
if proposed
else "policy result: refused -> Diagnosis and next checks -> opens an issue",
)
monitor.mark(
"response",
"Ariadne opens a pull request; nothing merges without a human"
if proposed
else "escalated; issue filed in the service repository",
)
if proposed:
monitor.mark(
"verify",
"the branch build validates the proposal; the incident stays"
" human-required either way",
)
if status == "healthy" and state.get("resolved") and not monitor.summarised:
monitor.mark("verify", "rebuild finished green")
monitor.mark("outputs", f"resolved: {', '.join(state['resolved'])}")
monitor.checklist()
monitor.summarised = True
status = str(states[-1].get("status") or "") if states else ""
if status and status != monitor.last_tick:
monitor.last_tick = status
print(f"{DIM}{stamp()} tick: {status}{RESET}")
sys.stdout.flush()
time.sleep(POLL_SECONDS)
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("\nstopped")

View File

@ -4,10 +4,10 @@
# Fallback: kubectl port-forward to service (OK for small/medium files).
#
# Usage:
# scripts/jellyfin_manual_load.fish <LOCAL_PATH> [REMOTE_SUBDIR] [JELLYFIN_API_KEY]
# scripts/ops/jellyfin_manual_load.fish <LOCAL_PATH> [REMOTE_SUBDIR] [JELLYFIN_API_KEY]
# Examples:
# scripts/jellyfin_manual_load.fish "$HOME/Downloads/Avatar - The Last Airbender (2005 - 2008) [1080p]" kids_tv "$JELLYFIN_API_TOKEN"
# scripts/jellyfin_manual_load.fish "$HOME/Movies/." movies # copy contents-only into /media/movies
# scripts/ops/jellyfin_manual_load.fish "$HOME/Downloads/Avatar - The Last Airbender (2005 - 2008) [1080p]" kids_tv "$JELLYFIN_API_TOKEN"
# scripts/ops/jellyfin_manual_load.fish "$HOME/Movies/." movies # copy contents-only into /media/movies
function usage
echo "Usage: "(basename (status filename))" <LOCAL_PATH> [REMOTE_SUBDIR] [JELLYFIN_API_KEY]"

View File

@ -1,3 +1,5 @@
#!/usr/bin/env fish
# Pick the correct K3s asset for a remote host (arm64 vs x86_64)
function __k3s_asset_for_host
set -l host $argv[1]

View File

@ -3,7 +3,7 @@ set -euo pipefail
usage() {
cat <<USAGE
Usage: scripts/node_recover.sh <node-name> [options]
Usage: scripts/ops/node_recover.sh <node-name> [options]
Options:
--yes Skip confirmation prompt

View File

View File

@ -16,7 +16,7 @@ from pathlib import Path
# Paths, folders, and shared metadata
# ---------------------------------------------------------------------------
ROOT = Path(__file__).resolve().parents[1]
ROOT = Path(__file__).resolve().parents[2]
DASHBOARD_DIR = ROOT / "services" / "monitoring" / "dashboards"
CONFIG_TEMPLATE = textwrap.dedent(
"""# {relative_path}
@ -110,8 +110,6 @@ WORKER_NODES = [
"titan-06",
"titan-07",
"titan-08",
"titan-09",
"titan-10",
"titan-11",
"titan-20",
"titan-21",
@ -119,7 +117,6 @@ WORKER_NODES = [
"titan-13",
"titan-14",
"titan-15",
"titan-16",
"titan-17",
"titan-18",
"titan-19",
@ -184,7 +181,8 @@ def scoped_node_expr(base, scope=""):
def node_cpu_expr(scope=""):
idle = 'avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m]))'
base = f"(1 - {idle}) * 100"
# Scrape stalls can briefly report an impossible idle rate after recovery.
base = f"clamp_max(clamp_min((1 - {idle}) * 100, 0), 100)"
return scoped_node_expr(base, scope)
@ -284,8 +282,14 @@ def dcgm_gpu_util_by_node():
)
def nvidia_gpu_util_by_node():
return "max by (node) (nvidia_gpu_device_utilization_percent)"
def gpu_util_by_node():
return f"{dcgm_gpu_util_by_node()} or {jetson_gpu_util_by_node()}"
process_exporter = nvidia_gpu_util_by_node()
dcgm_fallback = f"({dcgm_gpu_util_by_node()}) unless on(node) ({process_exporter})"
return f"{process_exporter} or {dcgm_fallback} or {jetson_gpu_util_by_node()}"
def gpu_util_by_hostname():
@ -295,16 +299,11 @@ def gpu_util_by_hostname():
GPU_RESOURCE_REGEX = "nvidia(_com_|[.]com/)gpu.*"
def gpu_node_labels():
return f'max by (node) (kube_node_status_allocatable{{resource=~"{GPU_RESOURCE_REGEX}"}} > bool 0)'
def gpu_requests_by_namespace_node(scope_var):
return (
"sum by (namespace,node) ("
f'kube_pod_container_resource_requests{{resource=~"{GPU_RESOURCE_REGEX}",{scope_var}}} '
"* on(namespace,pod) group_left(node) kube_pod_info "
f"* on(node) group_left() ({gpu_node_labels()})"
")"
)
@ -326,7 +325,8 @@ def gpu_usage_by_namespace(scope_var):
def jetson_gpu_usage_by_namespace(scope_var):
requests_by_ns = gpu_requests_by_namespace_node(scope_var)
total_by_node = f"sum by (node) ({requests_by_ns})"
all_requests = gpu_requests_by_namespace_node('namespace=~".*"')
total_by_node = f"sum by (node) ({all_requests})"
return (
"sum by (namespace) ("
f"({requests_by_ns}) / on(node) group_left() clamp_min({total_by_node}, 1) "
@ -335,6 +335,12 @@ def jetson_gpu_usage_by_namespace(scope_var):
)
def jetson_gpu_requested_nodes():
all_requests = gpu_requests_by_namespace_node('namespace=~".*"')
requested = f"(sum by (node) ({all_requests}) > 0)"
return f"({requested}) and on(node) ({jetson_gpu_util_by_node()})"
def namespace_share_expr(resource_expr):
total = f"clamp_min(sum( {resource_expr} ), 1)"
return f"100 * ( {resource_expr} ) / {total}"
@ -384,14 +390,29 @@ def gpu_total_devices_expr():
def unattributed_gpu_usage():
legacy_total = f"(sum({legacy_gpu_util_without_process_exporter()}) or on() vector(0))"
unresolved = (
f"({legacy_gpu_util_without_process_exporter()}) "
f"unless on(node) ({jetson_gpu_requested_nodes()})"
)
legacy_total = f"(sum({unresolved}) or on() vector(0))"
return (
f'label_replace(({legacy_total} > 0), "namespace", "unattributed", "", "")'
)
def gpu_utilization_raw(scope_var):
return f"({nvidia_process_gpu_usage_by_namespace(scope_var)}) or ({unattributed_gpu_usage()})"
nvidia = (
'label_replace('
f'{nvidia_process_gpu_usage_by_namespace(scope_var)}, '
'"gpu_source", "nvidia", "", "")'
)
jetson = (
'label_replace('
f'(({jetson_gpu_usage_by_namespace(scope_var)}) > 0), '
'"gpu_source", "jetson", "", "")'
)
attributed = f"sum by (namespace) (({nvidia}) or ({jetson}))"
return f"({attributed}) or ({unattributed_gpu_usage()})"
def gpu_pool_used_expr(scope_var):
@ -411,13 +432,20 @@ def namespace_gpu_share_expr(scope_var):
PROBLEM_PODS_EXPR = (
'sum(max by (namespace,pod) (kube_pod_status_phase{phase!~"Running|Succeeded"})) '
"or on() vector(0)"
'((sum(max by(namespace,pod) ('
'(kube_pod_status_phase{phase="Pending",namespace!~"veles"} == 1) '
'and on(namespace,pod) ((time() - kube_pod_created{namespace!~"veles"}) > 900)'
')) or on() vector(0)) + (sum(max by(namespace,pod) ('
'(kube_pod_status_phase{phase=~"Failed|Unknown",namespace!~"veles"} == 1) '
'unless on(namespace,pod) kube_pod_owner{owner_kind="Job"}'
')) or on() vector(0)))'
)
CRASHLOOP_EXPR = (
'sum(max by (namespace,pod) (kube_pod_container_status_waiting_reason'
'{reason=~"CrashLoopBackOff|ImagePullBackOff"})) '
"or on() vector(0)"
'sum(max by(namespace,pod) (kube_pod_container_status_waiting_reason'
'{namespace!~"veles",reason=~"CrashLoopBackOff|ImagePullBackOff"} '
'and on(namespace,pod) '
'((time() - kube_pod_created{namespace!~"veles"}) > 900))) '
'or on() vector(0)'
)
STUCK_TERMINATING_EXPR = (
'sum(max by (namespace,pod) ('
@ -428,21 +456,24 @@ STUCK_TERMINATING_EXPR = (
)
UPTIME_WINDOW = "365d"
# vmalert precomputes the expensive long-window rollup so Grafana only reads one compact series.
UPTIME_RECORDING_METRIC = f'atlas:availability:ratio_{UPTIME_WINDOW}{{scope="atlas"}}'
UPTIME_RECORDING_EXPR = f"last_over_time({UPTIME_RECORDING_METRIC}[24h])"
TRAEFIK_READY_EXPR = (
"("
'sum(kube_deployment_status_replicas_available{namespace=~"traefik|kube-system",deployment="traefik"})'
" / clamp_min("
'sum(kube_deployment_spec_replicas{namespace=~"traefik|kube-system",deployment="traefik"}), 1)'
")"
UPTIME_RECORDING_METRIC = (
f'atlas:availability:ratio_{UPTIME_WINDOW}{{scope="atlas",definition="request-v4"}}'
)
CONTROL_READY_FRACTION_EXPR = (
f"(sum(kube_node_status_condition{{condition=\"Ready\",status=\"true\",node=~\"{CONTROL_REGEX}\"}})"
f" / {CONTROL_TOTAL})"
AVAILABILITY_REQUESTS_1H_EXPR = (
'sum(increase(traefik_entrypoint_requests_total{'
'entrypoint="websecure",protocol="http",code=~"[1-5].."}[1h]))'
)
UPTIME_AVAIL_EXPR = (
f"min(({CONTROL_READY_FRACTION_EXPR}), ({TRAEFIK_READY_EXPR}))"
AVAILABILITY_FAILURES_1H_EXPR = (
'sum(increase(traefik_entrypoint_requests_total{'
'entrypoint="websecure",protocol="http",code=~"5.."}[1h]))'
)
UPTIME_LIVE_FALLBACK_EXPR = (
f"(1 - (({AVAILABILITY_FAILURES_1H_EXPR} or on() vector(0)) / "
f"clamp_min({AVAILABILITY_REQUESTS_1H_EXPR}, 1)))"
)
UPTIME_RECORDING_EXPR = (
f"(last_over_time({UPTIME_RECORDING_METRIC}[48h]) "
f"or on() {UPTIME_LIVE_FALLBACK_EXPR})"
)
# Tie-breaker to deterministically pick one node per namespace when shares tie.
@ -1870,9 +1901,9 @@ OVERVIEW_PANEL_DESCRIPTIONS = {
"Control Plane Ready": "Control-plane nodes currently Ready; full count is good, lower means Kubernetes core capacity is missing.",
"Control Plane Workloads": "Non-core pods running on control-plane nodes; zero is good because control nodes should stay focused.",
"Stuck Terminating": "Pods that Kubernetes cannot finish deleting; zero is good, growth means cleanup or storage may be stuck.",
"Atlas Availability (365d)": "Rolling one-year Atlas availability; higher is better, below target means users saw downtime.",
"Problem Pods": "Pods in unhealthy phases; zero is good, any count means a workload needs attention.",
"CrashLoop / ImagePull": "Pods restarting or unable to pull images; zero is good, any count usually blocks a service.",
"Atlas Availability (365d)": "Request-weighted Atlas ingress availability; every server-side 5xx response counts as a failed request.",
"Problem Pods": "Current-service pods Pending for more than 15 minutes or in an actionable failed phase. Completed Jobs and retained Veles migration workloads are kept on drill-down dashboards but excluded here.",
"CrashLoop / ImagePull": "Current-service pods stuck in CrashLoopBackOff or ImagePullBackOff for more than 15 minutes. Retained Veles migration workloads remain visible on the Pods dashboard.",
"Workers Ready": "Worker nodes currently Ready; full count is good, lower means less place to run services.",
"Hottest node: CPU": "Highest worker CPU load right now; lower is calmer, hot nodes may need pods moved.",
"Hottest node: RAM": "Highest worker memory use right now; lower is safer, high values risk evictions.",
@ -1910,7 +1941,7 @@ OVERVIEW_PANEL_DESCRIPTIONS = {
"Postgres Connections Used": "Current Postgres connections; lower leaves room for apps during spikes.",
"Postgres Hottest Connections": "Database with the most active connections; high values identify the pressure source.",
"Namespace CPU Share": "CPU share by namespace in the selected scope; big slices show who is using compute.",
"Namespace GPU Utilization": "Instant share of observed GPU compute activity by namespace. Host covers GPU work outside Kubernetes pods; idle appears only when observed GPU activity is zero.",
"Namespace GPU Utilization": "Current proportional share of observed GPU compute activity. Process-aware NVIDIA metrics attribute titan-22/24 work to namespaces and non-pod work to host. Jetson titan-20/21 compute is assigned by Kubernetes shared-GPU allocations; unallocated activity remains unattributed. The slices total 100% of compute in use now, independent of the selected dashboard time range; idle appears only when observed activity is zero.",
"Namespace RAM Share": "Memory share by namespace in the selected scope; big slices show who may drive pressure.",
"Worker Node CPU": "Worker CPU over time; lower is calmer, sustained high load may need rescheduling.",
"Worker Node RAM": "Worker memory over time; lower is safer, sustained high use risks evictions.",
@ -2111,7 +2142,7 @@ def build_overview():
"decimals": 4,
"text_mode": "value",
"instant": True,
"description": "Rolling 365-day availability from vmalert's precomputed atlas:availability:ratio_365d series. Grafana keeps the last successful rollup for up to 24h so one missed long-window evaluation does not render as No data.",
"description": "Rolling request-weighted availability at the Atlas HTTPS ingress: responses below 500 divided by all HTTP responses. Every server-side 5xx is a real failed request; client 4xx responses count as served. Replica counts, Grafana health, and monitoring gaps are not inputs, so they cannot lower availability unless user traffic actually receives a 5xx. A daily rollup job publishes one annual sample from deduplicated daily totals; Grafana keeps it for up to 48 hours so one delayed retry cannot cause a fallback, and only uses the same one-hour request SLI before history exists.",
},
{
"id": 4,
@ -2335,10 +2366,10 @@ def build_overview():
}
overview_avg_coverage = f"(avg(({QUALITY_GATE_COVERAGE_BY_SUITE})) or on() vector(0))"
overview_category_health = (
f'avg by (category) ({PLATFORM_TEST_CATEGORY_HEALTH_ROLLUP}{{'
f'(avg by (category) ({PLATFORM_TEST_CATEGORY_HEALTH_ROLLUP}{{'
f'suite=~"{PLATFORM_TEST_SUITE_CANONICAL_MATCHER}",branch!="",branch=~"main|master|origin/main|origin/master",'
f'category=~"{PLATFORM_TEST_OVERVIEW_CATEGORY_REGEX}"'
"})"
'})) or label_set(vector(0), "category", "none")'
)
for panel_id, title, draw_expr, runtime_expr, y_pos in [
(40, "Pyrphoros UPS Current", ANANKE_UPS_DRAW_WATTS_DB, ANANKE_UPS_RUNTIME_DB, 7),
@ -5017,11 +5048,100 @@ def build_jobs_dashboard():
}
HERMES_TRIAGE_OPEN_EXPR = (
'sum(ariadne_hermes_triage_incident{status="human_required"})'
)
HERMES_TRIAGE_RESOLVED_EXPR = (
'sum(increase(ariadne_hermes_triage_action_total{result="success"}[24h]))'
)
HERMES_TRIAGE_DIAGNOSIS_EXPR = (
'ariadne_hermes_triage_duration_seconds{phase="diagnosis"}'
)
HERMES_TRIAGE_ACTIONS_EXPR = (
"sum by (action, result) "
"(increase(ariadne_hermes_triage_action_total[1h]))"
)
HERMES_TRIAGE_INCIDENTS_EXPR = (
"sum by (jenkins_job, status) (ariadne_hermes_triage_incident) > 0"
)
def _hermes_triage_panels():
"""Return the Hermes automated-triage panels for the testing dashboard.
Inputs: none. Outputs: a list of Grafana panel definitions covering open
escalations, recent automated actions, diagnosis latency and per-job
incident state, sourced from Ariadne's bounded triage metrics.
"""
return [
stat_panel(
600,
"Triage Escalations Awaiting a Human",
HERMES_TRIAGE_OPEN_EXPR,
{"h": 4, "w": 6, "x": 0, "y": 100},
instant=True,
description=(
"Incidents Hermes diagnosed where Ariadne refused to act "
"automatically. Each one has a Gitea issue when its job is "
"mapped, and fires HermesTriageHumanRequired."
),
),
stat_panel(
601,
"Automated Actions Succeeded (24h)",
HERMES_TRIAGE_RESOLVED_EXPR,
{"h": 4, "w": 6, "x": 6, "y": 100},
instant=True,
description=(
"Allowlisted actions Ariadne executed and completed: fixture "
"repair, transient-infra retry, or a pushed patch proposal."
),
),
stat_panel(
602,
"Hermes Diagnosis Time (s)",
HERMES_TRIAGE_DIAGNOSIS_EXPR,
{"h": 4, "w": 6, "x": 12, "y": 100},
unit="s",
decimals=1,
instant=True,
description=(
"Wall-clock time of the most recent Hermes Agent run. This is "
"the pause between a red build and a diagnosis."
),
),
timeseries_panel(
603,
"Triage Actions by Result (1h rate)",
HERMES_TRIAGE_ACTIONS_EXPR,
{"h": 8, "w": 12, "x": 0, "y": 104},
legend="{{action}} · {{result}}",
description=(
"requested/accepted/rejected/success/failed per action id. A "
"rejected action means an authorization gate refused it."
),
),
timeseries_panel(
604,
"Incident State by Job",
HERMES_TRIAGE_INCIDENTS_EXPR,
{"h": 8, "w": 12, "x": 12, "y": 104},
legend="{{jenkins_job}} · {{status}}",
description=(
"Lifecycle of each incident: detected, diagnosed, repairing, "
"awaiting_rebuild, resolved, human_required or failed."
),
),
]
def build_testing_dashboard():
dashboard = build_jobs_dashboard()
dashboard["uid"] = "atlas-testing"
dashboard["folderUid"] = PUBLIC_DASHBOARD_FOLDER
dashboard["editable"] = False
dashboard["panels"] = list(dashboard["panels"]) + _hermes_triage_panels()
return dashboard
@ -5490,12 +5610,13 @@ def build_gpu_dashboard():
panels.append(
table_panel(
4,
"GPU Pods Reporting Device Util",
'topk(10, sum(DCGM_FI_DEV_GPU_UTIL{pod!=""}) by (namespace,pod,Hostname))',
"GPU Processes by Pod",
'topk(10, sum by (namespace,pod,node,process) '
'(nvidia_process_gpu_sm_util_percent{pod!="host"}) > 0)',
{"h": 8, "w": 12, "x": 12, "y": 8},
unit="percent",
transformations=[{"id": "labelsToFields", "options": {}}],
description="DCGM labels the device utilization sample with GPU-consuming pods; multiple pods on one device can report the same value.",
description="NVML process-level SM samples mapped to Kubernetes pods through host cgroups; values are per-process activity rather than duplicated whole-device utilization.",
)
)
return {

View File

@ -2,8 +2,8 @@
"""Generate OpenSearch Dashboards saved objects and render them into ConfigMaps.
Usage:
scripts/dashboards_render_logs.py --build # rebuild NDJSON + ConfigMap
scripts/dashboards_render_logs.py # re-render ConfigMap from NDJSON
scripts/render/dashboards_render_logs.py --build # rebuild NDJSON + ConfigMap
scripts/render/dashboards_render_logs.py # re-render ConfigMap from NDJSON
"""
from __future__ import annotations
@ -14,14 +14,21 @@ import textwrap
from dataclasses import dataclass
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def repo_root() -> Path:
for path in Path(__file__).resolve().parents:
if (path / "clusters").is_dir() and (path / "services").is_dir():
return path
raise RuntimeError("could not locate repository root") # pragma: no cover
ROOT = repo_root()
DASHBOARD_DIR = ROOT / "services" / "logging" / "dashboards"
NDJSON_PATH = DASHBOARD_DIR / "logs.ndjson"
CONFIG_PATH = ROOT / "services" / "logging" / "opensearch-dashboards-objects.yaml"
CONFIG_TEMPLATE = textwrap.dedent(
"""# {relative_path}
# Generated by scripts/dashboards_render_logs.py --build
# Generated by scripts/render/dashboards_render_logs.py --build
apiVersion: v1
kind: ConfigMap
metadata:

View File

@ -0,0 +1,85 @@
#!/usr/bin/env python3
"""Print a compact inventory of Flux Kustomization resources."""
from __future__ import annotations
import argparse
from pathlib import Path
from typing import Any
import yaml
def _iter_docs(path: Path) -> list[dict[str, Any]]:
docs: list[dict[str, Any]] = []
for raw in yaml.safe_load_all(path.read_text(encoding="utf-8")):
if isinstance(raw, dict):
docs.append(raw)
return docs
def _is_flux_kustomization(doc: dict[str, Any]) -> bool:
return (
doc.get("kind") == "Kustomization"
and str(doc.get("apiVersion") or "").startswith("kustomize.toolkit.fluxcd.io/")
)
def _depends_on(spec: dict[str, Any]) -> str:
names = []
for dep in spec.get("dependsOn") or []:
if isinstance(dep, dict) and dep.get("name"):
names.append(str(dep["name"]))
return ",".join(names)
def _row(doc: dict[str, Any], path: Path) -> dict[str, str]:
metadata = doc.get("metadata") or {}
spec = doc.get("spec") or {}
annotations = metadata.get("annotations") or {}
return {
"name": str(metadata.get("name") or ""),
"path": str(spec.get("path") or ""),
"namespace": str(spec.get("targetNamespace") or ""),
"depends": _depends_on(spec),
"prune": str(spec.get("prune", "")),
"wait": str(spec.get("wait", "")),
"suspend": str(spec.get("suspend", False)),
"reason": str(annotations.get("atlas.bstein.dev/suspend-reason") or ""),
"file": str(path),
}
def build_inventory(root: Path) -> list[dict[str, str]]:
rows: list[dict[str, str]] = []
for path in sorted(root.rglob("*.yaml")):
for doc in _iter_docs(path):
if _is_flux_kustomization(doc):
rows.append(_row(doc, path))
return sorted(rows, key=lambda item: item["name"])
def _print_table(rows: list[dict[str, str]]) -> None:
columns = ["name", "path", "namespace", "depends", "prune", "wait", "suspend", "reason"]
widths = {
column: max([len(column), *[len(row[column]) for row in rows]]) for column in columns
}
header = " ".join(column.ljust(widths[column]) for column in columns)
print(header)
print(" ".join("-" * widths[column] for column in columns))
for row in rows:
print(" ".join(row[column].ljust(widths[column]) for column in columns))
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("root", nargs="?", default="clusters/atlas/flux-system")
args = parser.parse_args()
rows = build_inventory(Path(args.root))
_print_table(rows)
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@ -25,7 +25,14 @@ from typing import Any, Iterable
import yaml
REPO_ROOT = Path(__file__).resolve().parents[1]
def repo_root() -> Path:
for path in Path(__file__).resolve().parents:
if (path / "clusters").is_dir() and (path / "services").is_dir():
return path
raise RuntimeError("could not locate repository root") # pragma: no cover
REPO_ROOT = repo_root()
DASHBOARD_DIR = REPO_ROOT / "services" / "monitoring" / "dashboards"
CLUSTER_SCOPED_KINDS = {
@ -580,7 +587,7 @@ def main() -> int:
catalog_rel = catalog_path.relative_to(REPO_ROOT).as_posix()
catalog_path.write_text(
f"# {catalog_rel}\n"
"# Generated by scripts/knowledge_render_atlas.py (do not edit by hand)\n"
"# Generated by scripts/render/knowledge_render_atlas.py (do not edit by hand)\n"
+ yaml.safe_dump(catalog, sort_keys=False),
encoding="utf-8",
)

View File

@ -2,8 +2,8 @@
"""Generate OpenSearch Observability seed objects and render them into ConfigMaps.
Usage:
scripts/logging_render_observability.py --build # rebuild JSON + ConfigMap
scripts/logging_render_observability.py # re-render ConfigMap from JSON
scripts/render/logging_render_observability.py --build # rebuild JSON + ConfigMap
scripts/render/logging_render_observability.py # re-render ConfigMap from JSON
"""
from __future__ import annotations
@ -14,7 +14,14 @@ import textwrap
from dataclasses import dataclass
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def repo_root() -> Path:
for path in Path(__file__).resolve().parents:
if (path / "clusters").is_dir() and (path / "services").is_dir():
return path
raise RuntimeError("could not locate repository root") # pragma: no cover
ROOT = repo_root()
OBS_DIR = ROOT / "services" / "logging" / "observability"
APPS_PATH = OBS_DIR / "applications.json"
QUERIES_PATH = OBS_DIR / "saved_queries.json"
@ -23,7 +30,7 @@ CONFIG_PATH = ROOT / "services" / "logging" / "opensearch-observability-objects.
CONFIG_TEMPLATE = textwrap.dedent(
"""# {relative_path}
# Generated by scripts/logging_render_observability.py --build
# Generated by scripts/render/logging_render_observability.py --build
apiVersion: v1
kind: ConfigMap
metadata:

Some files were not shown because too many files have changed in this diff Show More