267 Commits

Author SHA1 Message Date
jenkins
8acb99d100 hermes: make Claude setup-token access durable 2026-08-22 22:52:28 -03:00
jenkins
db0cb86ca6 hermes: stabilize Claude access health probes 2026-08-22 18:07:58 -03:00
jenkins
bd50a7e4ad hermes: persist Claude subscription access 2026-08-22 17:32:30 -03:00
jenkins
9bf41dc0de hermes: decouple node maintenance health 2026-08-22 16:39:44 -03:00
jenkins
fdf53b264b hermes: fit owner pod on healthy workers 2026-08-22 15:55:30 -03:00
jenkins
46a44241c1 hermes: reclaim healthy worker capacity 2026-08-22 15:42:56 -03:00
jenkins
8a8df5ee4f hermes: add stateful accelerator fallback 2026-08-22 15:32:21 -03:00
ed98278981 Merge branch 'main' into feature/hermes-domain-rename-agent-worker-v2
Some checks failed
Tests / Declarative: Post Actions failed: 40, skipped: 19, passed: 2743
2026-08-21 23:15:16 +00:00
77905ff1db Merge branch 'main' into fix/cli-auto-failover-effort
Some checks failed
Tests / Declarative: Post Actions failed: 40, skipped: 19, passed: 2737
2026-08-21 23:12:12 +00:00
Hermes Agent
820872e117 feat(hermes-voice): route Whisper language to multilingual Piper
Port the original #27 detected-language pipeline onto the verified PR #39 prerequisite while preserving the current-main conversation instrument and host continuity changes.

Keep voice selection server-side with no user selector or client voice field. Reuse 207c16ab only for its stricter exact-code trust boundary, omitting malformed or absent language so Piper defaults to Amy.
2026-08-21 13:58:50 +00:00
Hermes Agent
724656d841 feat(hermes-tts): prepare fixed multilingual voice policy
Supersede draft PR #26 with a merge-safe prerequisite: bake and preload the amy, irina, and claude Piper models, route only validated server-side language to fixed voices, and leave the live voice deployment manifest unchanged.

Remove the pinned WebUI speaker selector and its persisted preference, omit client voice fields from every outbound TTS path, and keep hands-free Voice Mode and the conversation instrument intact. Hostile or legacy voice fields remain ignored by the Piper server.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 13:45:31 +00:00
Hermes Agent
cfd8a75e95 fix(hermes-voice): preserve MediaRecorder container headers 2026-08-21 11:12:15 +00:00
Hermes Agent
94106bf252 refactor(hermes): rename the owner agent host to worker.bstein.dev
Introduce worker.bstein.dev as the canonical hostname for the owner-only
Hermes coordinator, previously agent.hermes.bstein.dev.

The rename is additive, matching the shape #38 restored for chat and triage.
CoreDNS, both agent Ingresses and the hermes-sites certificate now serve BOTH
names, so merging this cannot take away the endpoint the operator uses to
reach the coordinator. Retiring agent.hermes.bstein.dev is a separate,
separately scheduled change. No redirect middleware is added.

What switches to the new host:
- HERMES_DASHBOARD_PUBLIC_URL and the oauth2-proxy --redirect-url
- the Keycloak agent proxy rootUrl
- operator docs, skills, the ZAP baseline target and the triage monitor default

What stays dual-homed until retirement:
- CoreDNS hosts entry, both agent Ingress rules, certificate SANs
- API_SERVER_CORS_ORIGINS (now a comma-separated pair)
- the Keycloak redirect URIs, web origins and post-logout origins, so a
  rollback only needs the oauth2-proxy --redirect-url reverted and does not
  require re-running the ensure job

The agent client passes its legacy origin through the optional fourth argument
#38 added to ensure_proxy_client, so no second mechanism is introduced. The
immutable ensure Job goes -11 -> -12 because #38 already consumed -11 and that
run has completed; without a further bump this change would never be applied.
Login on the new host fails until the -12 Job completes.

Because the session and CSRF cookies use the __Host- prefix they are bound to
one origin, so a fresh login must start on worker.bstein.dev and existing
sessions do not carry over -- re-login is required after rollout.

#38's public-host continuity test now covers the agent proxy's dual origins
rather than asserting the agent surface was untouched by the rename.

Knowledge catalogs and diagrams regenerated with `make knowledge`.
2026-08-21 10:29:46 +00:00
Hermes Agent
fd4bf69007 test(hermes): pin public chat/triage host continuity across every layer
The #34 rename dropped the legacy chat/triage names from the certificate
SANs, the hermes-sites Ingress, the CoreDNS overrides and the Keycloak
ensure script at the same time, so nothing failed loudly: DNS and TLS
still looked healthy while the legacy hosts served 404 and the renamed
hosts could not finish a login.

Pin the invariant that makes that silent: a public host is either served
by all four layers or by none. The table of hosts is the contract, so
retiring a name stays a deliberate edit rather than a side effect.

Verified to catch the regression: against the pre-fix tree these fail for
both legacy hosts on all four layers (9 failures); against this branch
the suite is green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 08:57:50 +00:00
Hermes Agent
4277aa6a02 fix(hermes): allow two direct CLI lane workers 2026-08-21 07:11:55 +00:00
Hermes Agent
d579c08cbe fix(hermes): cli-auto capacity failover uses automatic Switchyard reclassification
Capacity/auth/quota failover for cli-auto previously called select_route
with a hardcoded manual lane (cli-{alternate}-{effort}), so the retry
boundary was classified as switchyard-manual instead of going through
Jetson automatic classification, making the routing evidence misleading.

Now the retry calls select_route(context, "cli-auto", exclude_provider=...)
so the boundary stays automatically classified with an explicit
failed-provider exclusion. If the classifier reclassifies to a lower
effort than the original route, the lane re-pins the chosen provider at
the original effort floor so a capacity failure never silently downgrades
a high/xhigh task. Manual lanes (assignee != cli-auto) remain unchanged
and still fail closed without switching providers.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 05:04:55 +00:00
Hermes Agent
c8f53ecdf5 feat(hermes-voice): add conversation state instrument 2026-08-20 23:13:51 +00:00
Hermes Agent
d22588dddb fix(hermes): poll the session contract the chat tenants actually serve
Returning to chat.hermes.bstein.dev after a Keycloak logout/login showed
"This session is unavailable to this account. Start a new chat." even
though the session was intact and owned by the same subject.

The banner comes from the continuity fallback the router injects into
every chat page. It polled `/api/sessions/<id>` and
`/api/sessions/<id>/messages` — routes that belong to the Hermes agent
dashboard (added by scripts/patch_web_session_activity.py, applied only
in agent-deployment.yaml). The router proxies browser traffic to the
tenant Hermes WebUI instead, whose only session read is
`GET /api/session?session_id=<id>`; the dashboard paths are unrouted
there, so server.py answered its generic 404 for every poll and the
fallback reported a false ownership failure.

The script runs only on a full document load of `/session/<id>`, which is
exactly what the OIDC round-trip produces when oauth2-proxy returns the
browser to `rd=/session/<id>` — hence the "only after relogin" symptom.

Poll the WebUI contract instead, and let its own answers decide what the
banner claims: 409 `session_profile_mismatch` is the single response that
means the session is outside this account's active scope, 404 now means
the conversation is no longer stored, and 401/403 still re-enter OIDC.
The steady-state poll drops to one request and backs off to 3s/15s now
that it reaches a real endpoint on the tenant Raspberry Pi.

`boundSessionSnapshot` follows the same move: it caps the WebUI envelope
`{"session": {..., "messages": [...]}}`, relaying every other session key
verbatim rather than re-serializing a fixed struct that would silently
drop metadata the banner depends on.

Isolation is unchanged and now covered: the router still resolves the
slot from the salted Keycloak subject, overwrites any client-supplied
X-Hermes-Tenant-Identity, and forwards only the two tenant cookies.

Tests: relogin keeps a stable slot and resolves the durable session; a
second subject replaying the owner's session id, WebUI cookie and a
forged tenant header gets 404 from its own backend and never reaches the
owner's; the legacy dashboard paths are pinned as permanent 404s against
a stub of the deployed WebUI dispatch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 18:14:55 +00:00
jenkins
b6ae6225f6 hermes: source handoff forge evidence through the scm broker
The acceptance harness pinned a forge client that has never existed in any
commit or pod (/opt/coordinator/gitea_api.py, digest f0943db4..., GIT/POST
grammar, an askpass helper). Every Gitea-backed check was therefore
unrunnable as merged. Point the harness at the credential-isolated SCM
broker client that actually ships in the agent pod.

- policy: GITEA_CLIENT=/opt/scm/gitea_api.py; trust /opt/scm/ instead of
  the phantom /opt/coordinator/; admit the client's real grammar
  (`read <api-path>`, exactly one path) with the same atlas/titan-iac pin
  and dot-segment rejection; bare HTTP methods are refused in every mode.
  The armed POST/PATCH/DELETE windows remain but are documented as
  deferred: the deployed client cannot execute them.
- exec: pin the client digest to the sha256 of
  services/hermes/scm-common/scripts/gitea_api.py — the exact file the
  hermes-scm-boundary-v2 ConfigMap mounts at /opt/scm/gitea_api.py — so
  the pin is derivable from merged source and equal to the deployed
  client. gitea_api.py gains a narrow /api/v1/user identity read in
  _authorize_read (see below), so the pin is the NEW source hash
  76efd16dedbeb74425b12fbbdbfaa391854771292077e0463bf22706855ae6dc.
  Drop the dangling GIT_ASKPASS (no helper exists; broker git needs
  none) and swap /opt/coordinator for /opt/scm in SAFE_PATH.
- checks: all forge/baseline/lineage probes use (client, "read", path).
  The SELF-vantage identity checks now truthfully assert the *broker's*
  forge identity (the only one the platform can exercise) is not an
  administrator and holds push-scoped, non-administrative repository
  authority; the administrative-route check asserts the broker read
  allowlist's live refusal of branch_protections. The remote-main step
  keeps `origin` (the broker remote exists only in pool workspaces and
  the broker origin is cluster-local); its rationale now tells the
  operator to ensure origin fetchability.
- gitea_api.py/_authorize_read: allow exactly `/api/v1/user` (no query,
  no sibling routes) as operation "identity" so the harness can prove
  the broker identity is not an administrator. The broker imports the
  same module, so one reviewed edit covers both sides of the boundary.
- rules: DENIAL_MARKERS now match the client's real refusal lines
  ("SCM broker request failed with HTTP 400/403" and the no-credential
  rejection) and drop "gitea api returned http 403", which the client
  never emits; a broker 404 is deliberately not denial evidence.
- ephemeral: index/verification reads use the real grammar; manual
  cleanup guidance now says close/delete require operator forge
  credentials (the client exposes no mutation besides create-draft);
  armed mode is documented as deferred until the probes are rebuilt on
  the broker's bounded mutation surface.
- docs: broker vantage/evidence section, operator prerequisites (broker
  healthy, no /vault/secrets/gitea-token anywhere on the harness path,
  current ConfigMap mount, operator-side client + origin fetchability),
  armed-mode deferral.
- tests: read-grammar accepted / GET refused in every mode, /opt/scm
  attestation pin proven equal to the merged source digest, real
  denial-marker matching, /api/v1/user identity route bounds; the
  repository-pin mutant probe speaks the new grammar. Full handoff +
  gitea + broker families pass (952 tests), mutation gate 13/13, per-file
  line+branch coverage >=95%, all touched sources within the 500-line cap.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-18 18:21:38 -03:00
jenkins
0a4ff7f1f6 hermes: read node access keys through CSI symlinks
The Vault CSI secrets-store projects /vault/secrets/node-ssh-public-key
as a symlink chain (file -> ..data/file -> ..<timestamp>/file), which
the O_NOFOLLOW hardened reader refuses with ELOOP, crash-looping the
hermes-node-ssh-access key reconciler on most nodes.

Resolve the projection safely instead of relaxing the reader: realpath
the secret strictly (loops and dangling links become a clean refusal),
refuse any resolution that escapes the read-only mount, then apply the
unchanged O_NOFOLLOW regular-file read to the resolved target. Host
account databases and authorized_keys keep the strict no-symlink read.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-18 16:28:25 -03:00
jenkins
a5cafddd45 hermes: accept merged lineage in handoff acceptance
The reviewed PR stack is now merged into main, so an open draft PR #19 is
no longer proof that the reviewed code is what runs. The mandatory
release.exact-lineage-is-running check now requires the merged terminal
state instead: PR #19 closed with merged=true, base main, the existing
feature ref, and the exact reviewed head, plus a new merge-ancestry step
that proves the reviewed head is an ancestor of the pinned origin/main
via git merge-base --is-ancestor. An open PR, a PR closed without
merging, a mismatched head, or a head that is not a proven ancestor of
main still fails closed; an undecidable ancestry probe is NOT_RUN. The
recorded pre-merge base SHA is no longer compared against current main.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-18 16:28:25 -03:00
jenkins
9749e65042 Merge remote-tracking branch 'origin/feature/hermes-chat-smoothness' into feature/hermes-inpod-supervisor 2026-08-18 07:03:41 -03:00
jenkins
c8bb23ccd3 hermes: surface a friendly "Automatic" routing label
The composer routing chip showed bare AUTO/FAST/BALANCED/DEEP/MAXIMUM.
Relabel it to Automatic/Fast/Balanced/Deep/Maximum and align the
auto-route model option labels to Automatic · Fast/Balanced/Deep/Maximum
so they match the panels.js profile-default mapping and read as a
recognizable model name. Manual model names (Claude · Opus, etc.) are
unchanged. Update the image-build guard and its quality test to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-18 06:55:51 -03:00
jenkins
d1e8cda5ed hermes: make supervisor terminal emissions idempotent across ticks
Fix reviewer BLOCK: the stateless poll loop re-planned every terminal card each
tick, so SHIP and the two cycle-limit escalations - whose source cards stay in
the done state - re-fired their comment/block on every pass (~2880/day/chain).

- Add a persistent emission Ledger (/opt/data/supervisor/emitted.json): SHIP and
  escalate perform their action only when the (action, target) key is absent,
  then record it, so each fires at most once and survives a pod restart. Spawns
  remain self-limiting via the existing dedup scan.
- Cycle-limit escalations now target the SOURCE review/repair card (not the impl
  root), so the card also leaves the done state and is skipped next tick even if
  block_task round-trips imperfectly.
- Harden the _spawn TypeError fallback: re-run the dedup scan before retrying so
  a post-insert TypeError can never double-insert.
- Add across-ticks idempotency tests (10x supervise_once -> exactly one
  comment/flag/block) plus ledger persistence/corruption and spawn-guard tests.

Both modules stay 100% line+branch, <500 LOC. Stacks on the merge train
(base 5f27e50c).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-18 06:51:36 -03:00
jenkins
2b2c8bda14 Merge remote-tracking branch 'origin/wt/t_1e95ea6d' into feature/hermes-chat-smoothness 2026-08-18 06:46:48 -03:00
jenkins
a9ee1070be Merge remote-tracking branch 'origin/feature/hermes-full-handoff-acceptance' into wt/t_1e95ea6d
# Conflicts:
#	services/hermes/agent-deployment.yaml
#	testing/tests/test_hermes_auto_router.py
2026-08-18 06:41:13 -03:00
jenkins
6a7009b31c hermes: add in-pod autonomous kanban supervisor
Drive the cross-card implement->review->repair->re-review chain from inside
the pod so unattended runs no longer stall once the first implementation card
completes. Today that chain exists only as an external codex-shepherd session;
this adds a bounded in-pod poll loop that reads board state via
hermes_cli.kanban_db and creates Kanban follow-up cards (subscription lanes
only) with no provider/metered path of its own.

- kanban_supervisor.py (I/O shell) + supervisor_policy.py (pure state machine):
  impl-done+PR -> review; review SHIP -> mark impl ready-for-human (never
  merges/approves/clears WIP); review BLOCK -> bounded repair; repair new
  commit -> re-review. Fail-closed on unparseable/ambiguous state; per-
  (parent, head_commit) dedup safe beside the external shepherd; bounded review
  <->repair cycle count and max concurrent chains.
- Deployed as a hardened non-root sidecar (drop ALL caps, read-only rootfs, no
  runtime-access/credential mount) alongside model-steward; scripts packaged in
  the coordinator configMapGenerator.
- Gated by new kanban.auto_supervise config key (default false, re-read each
  tick like auto_decompose) so it is inert until the external shepherd retires.
- Fix latent goal_max_turns NULL fallback in cli_lane_execution (1 -> documented
  default 20).
- 62 new behavioral tests at 100% line+branch on both modules.

Stacks on the merge train (base 5f27e50c).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-18 06:28:49 -03:00
jenkins
943ecfd15b Merge remote-tracking branch 'origin/feature/hermes-distributed-worker-pool' into feature/hermes-full-handoff-acceptance
# Conflicts:
#	ci/scripts/semgrep_report.py
#	testing/quality_contract.json
#	testing/quality_coverage.py
#	testing/tests/conftest.py
#	testing/tests/test_hermes_auto_router.py
#	testing/tests/test_quality_contract.py
#	testing/tests/test_quality_coverage_helpers.py
#	testing/tests/test_semgrep_report.py
2026-08-18 06:28:18 -03:00
jenkins
5f27e50c74 hermes: reconcile pool tests and staging hygiene with the train API
Post-merge fixes after rebasing the distributed worker pool onto the
review-goal-semantics train tip:

- Pool SCM submission tests target the train's relocated receive-pack
  scanner: FEATURE_REF_RE now lives in receive_pack_scan, bodies are
  built via the shared _receive_command helper (valid pack), and the
  update-rejection assertion matches the train's message.
- Take the train's canonical test_hermes_scm_broker,
  test_hermes_cli_dispatch_runtime and test_hermes_cli_execution_edges,
  which exercise the train's broker/dispatch/execution behavior.
- Runtime staging tests patch os.fchown alongside os.chown so the
  UID-10000 _write_secret path passes under a non-root gate runner
  (production ownership behavior unchanged).
- Split the jenkins build-evidence contracts out of
  test_hermes_runtime_access into test_hermes_runtime_evidence to keep
  both files under the 500-LOC hygiene ceiling after the merge.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-18 02:00:19 -03:00
jenkins
8036101f0b Merge remote-tracking branch 'origin/feature/hermes-review-goal-semantics' into feature/hermes-distributed-worker-pool
# Conflicts:
#	scripts/tests/test_dashboards_render_atlas_drilldowns.py
#	scripts/tests/test_dashboards_render_jobs.py
#	services/hermes/scm-common/scripts/scm_broker.py
#	services/hermes/scripts/cli_lane_dispatch.py
#	services/hermes/scripts/cli_lane_execution.py
#	testing/quality_contract.json
#	testing/tests/test_hermes_agent_access.py
#	testing/tests/test_hermes_agent_security.py
#	testing/tests/test_hermes_chat_config.py
#	testing/tests/test_hermes_chat_images.py
#	testing/tests/test_hermes_chat_provider_auth.py
#	testing/tests/test_hermes_chat_quality.py
#	testing/tests/test_hermes_chat_support.py
#	testing/tests/test_hermes_chat_voice.py
#	testing/tests/test_hermes_cli_finalization_edges.py
#	testing/tests/test_hermes_cli_foundation_coverage.py
#	testing/tests/test_hermes_cli_lanes_configuration.py
#	testing/tests/test_hermes_cli_recovery_edges.py
#	testing/tests/test_hermes_cli_retention_edges.py
#	testing/tests/test_hermes_coordinator.py
#	testing/tests/test_hermes_coordinator_boards.py
#	testing/tests/test_hermes_coordinator_support.py
2026-08-18 01:43:39 -03:00
jenkins
7db9f61659 Merge remote-tracking branch 'origin/feature/hermes-cli-auto-quota-routing' into feature/hermes-review-goal-semantics
# Conflicts:
#	services/hermes/scripts/cli_lane_runner.py
2026-08-18 01:18:06 -03:00
jenkins
603524ba6a Merge remote-tracking branch 'origin/fix/hermes-result-decomposition-reliability' into feature/hermes-cli-auto-quota-routing
# Conflicts:
#	services/hermes/networkpolicy.yaml
#	testing/quality_contract.json
#	testing/tests/test_hermes_agent_security.py
2026-08-18 00:57:54 -03:00
jenkins
a3077224e0 Merge origin/main (#14 + #16) into PR #15 decomposition reliability
Reconcile two independent test/gate reorganizations:
- Gate/semgrep/mailu: keep main's #16 dual-metric implementation.
- quality_contract.json: union #16 image-builder + #14 scm/node + #15 cli_lane.
- agent-deployment.yaml: keep #14 gitea removal + #16 image-build-token + #15 probe.
- Test splits: main's chat/coordinator/agent organization is authoritative;
  drop #15's redundant competing splits and #14's stale cli-lane duplicates;
  keep #15's cli-lane decomposition suite and port the execution-safety test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-18 00:42:53 -03:00
jenkins
9d42ff33f5 Merge origin/main (#16 image-builder) into hermes-safe-gitea-pr
Resolve 8 conflicts, uniting #16's daemonless image-release lane with
#14's SCM broker + node-audit boundary. Nothing dropped from either side.

- quality_coverage.py / test_quality_coverage_helpers.py: take main's
  dual-metric gate (line+branch enforced per file at minimum_percent);
  drop #14's now-dead branch_tracked_files/minimum_branch_percent keys.
- quality_contract.json: union all lists (managed_modules, lint_paths,
  coverage_sources, tracked_files, hygiene globs) so #14's scm-broker,
  git_pack_objects, receive_pack_scan, deadline_http and node_polkit_audit
  are branch-checked alongside #16's image-builder modules.
- Vault auth (vault_k8s_auth_configure.sh): coexist both role sets. Keep
  #16's hermes-switchyard split + hermes-jenkins-token-seed; keep #14's
  hermes-scm-broker role. Preserve #14's security property: hermes-agent
  no longer holds developer-gitea (broker role carries it).
- agent-deployment.yaml / stage_runtime_access.py: keep #16's
  jenkins-image-build-token injection/staging; keep #14's removal of the
  agent's gitea-token/gitea-username.
- Bump vault-k8s-auth-hermes job -9 -> -10 (and its health check + test)
  so the merged auth config re-applies over any -9 already in-cluster.
- flux hermes dependsOn: union jenkins + hermes-scm-broker + observer-rbac.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 21:07:06 -03:00
jenkins
a5d0abb678 hermes: align capacity gate with auth classifier
CAPACITY_PATTERN (the gate that sets result.capacity_failure) lacked the
bare unauthorized/forbidden/401/403 signals that classify_capacity_failure
already recognizes, so an auth blip surfacing only as "403 Forbidden"
was blocked as capability instead of transient: no auto failover, no
health cooldown. Add 401|403|unauthorized|forbidden to the gate so it
matches the classifier; reason classification still distinguishes auth
from quota/rate-limit/transport.

Tests: an auto card failing with only "403 Forbidden" now fails over to
the other provider, records an auth cooldown (authenticated:false), and
classifies the fallback reason as auth in metrics; a manual card with the
same failure still fails closed as transient. Router-outage tests moved to
test_hermes_cli_router_outage.py to keep both files <500 LOC.

Based on PR #15 (fix/hermes-result-decomposition-reliability).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-17 20:46:00 -03:00
jenkins
31439f096a hermes: bound streaming git DNS resolution
The watchdog can only close a live socket, but getaddrinfo runs before
any socket exists and ignores socket timeouts, so a slow resolver
outlived the stream deadline. Resolve under the same absolute deadline
in a joinable worker before the real connect.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 20:37:30 -03:00
jenkins
6c1123201e hermes: audit every sudo include spelling
A quoted, space-bearing #include path failed the include regex and fell
through to the comment branch, leaving a second authority file
unenumerated. Detect any include directive before the comment rule and
fail closed on every form except the exact bare includedir into the
audited /etc/sudoers.d.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 20:37:30 -03:00
jenkins
6e9e8c6ab5 hermes: reject regex literals in polkit grants
A regex literal embedding a quote desynced the string tokenizer and
swallowed an unconditional Result.YES, so the grant read as unscoped and
slipped past the literal-identity check. Fail closed on any slash outside
a string or comment; neither regex nor division belongs in an
identity-scoped grant.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 20:37:30 -03:00
jenkins
6df688adcc hermes: test quota gating, cooldown recovery, and failover policy
Deterministic coverage for the quota-aware lane: threshold boundaries
(14.9/15/15.1), both-below preference, fetch-failure fail-open, cooldown
elapsed-vs-not hysteresis, quota-reset recovery (never for auth),
explicit fail-closed in both directions, bounded double-failure block,
failure-reason classification, metrics emission, and worker env key
stripping. Based on PR #15 (fix/hermes-result-decomposition-reliability).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-17 20:31:12 -03:00
jenkins
034c8372c7 hermes: enforce quota-aware fail-closed cli-auto provider routing
Based on PR #15 (fix/hermes-result-decomposition-reliability); stacked
on the decomposed cli_lane modules.

- cli_lane_quota: soft-exclude a provider from NEW cli-auto work below
  the remaining-quota threshold (both-below prefers more remaining;
  fetch failure fails open with a metric).
- cli_lane_health: lane now writes provider health (G7) with classified
  failure reasons splitting the capacity conflation (quota/auth/
  rate-limit/transport) and cooldown hysteresis; re-admission only on
  full cooldown expiry, passed quota reset, or fresh success (G4).
- cli_lane_routing: capacity-limited health now excludes a provider
  (G3); cooldown/reset-aware re-admission.
- cli_lane_failover: explicit cli-codex-*/cli-claude-* assignees fail
  closed as transient instead of switching providers (G5); fallback
  depth stays bounded at two hosted providers (G1) with effort
  preserved; Switchyard outages block transient, not capability (G9).
- cli_lane_metrics: route-decision/fallback counters, quota and
  soft-exclusion gauges, pod-local scrape server (G6).
- cli_lane_provider: worker env drops ANTHROPIC_API_KEY, CLAUDE_API_KEY,
  OPENAI_API_KEY, API_SERVER_KEY so no metered path exists (G10).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-17 20:31:05 -03:00
jenkins
6f993c049e hermes: keep agent web reachable during lane deferral
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 15:20:43 -03:00
jenkins
5020e49057 hermes: track new boundary modules in gate
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 15:15:47 -03:00
jenkins
b8f2163af0 hermes: prove sudo and polkit denial closed
Expand sudoers User_Alias chains so aliases, wildcards, netgroups, and
undefined names cannot smuggle authority to the Hermes account, and
require polkit grants to scope through exact literal identity
comparisons: computed strings, bracket lookups, subject aliasing,
operator-built values, and unconditional or wildcard grants fail closed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 15:15:47 -03:00
jenkins
340ec58b68 hermes: quarantine-scan pushed git objects
Inflate every receive-pack object under strict pack, size, and checksum
bounds, resolve deltas against in-pack bases only, and scan the real
decompressed payloads for runtime-token forms, private keys, SSH key
material, and known provider token formats. Thin packs are rejected so
no pushed content escapes the scan, and upstream Git exchanges now run
under one absolute stream deadline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 15:15:47 -03:00
jenkins
578239a496 hermes: bound SCM control calls with hard deadlines
Run every Gitea API and broker control exchange inside a killable helper
process whose connect, send, and read share one absolute wall-clock
deadline, and add a watchdog that force-closes streaming connections at
expiry. Redirects are rejected before authentication headers can move.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 15:15:35 -03:00
jenkins
2019b08276 gitea: require literal branch protection rules
Replace the reimplemented gobwas/glob matcher with exact-literal rule
matching that fails closed on any special or malformed pattern, accepts
legacy zero priorities, and rejects duplicate primary-branch rules.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 15:15:35 -03:00
jenkins
f77119b238 hermes: enforce per-file branch coverage in the quality gate
The coverage check read only line-rate, so a file could pass with weak
branch coverage. Load both Cobertura rates and fail any tracked file
below the 95% floor on either metric, failing closed when branch
evidence is absent. Prove enforcement end to end with a synthetic
fully-lined but branch-weak file failing run_profile.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 15:10:17 -03:00
jenkins
64272f52d2 hermes: lift remaining tracked modules to the branch floor
Exercise the mailu sync retry, attribute, and skip branches, the
listener non-object JSON path, and the hygiene conftest skip; drop the
unreachable inverted-range clamp in the semgrep report (the line helper
already floors the end line) and pin that behavior with a test. Exclude
the mailu __main__ guards from measurement.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 15:10:17 -03:00
jenkins
3a57371989 hermes: close release-lane branch coverage gaps
Cover the uncovered branches in the image release script (redirect
handler, policy-read credential and size bounds, count and shape
rejections, EOF manifest scan) and in the Harbor immutability helper
(scope-failure verification, absent robot duration, stale-read retries,
created-rule ID binding, bounded startup retries). Exclude the
__main__ guard like the sibling release script.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 15:10:17 -03:00
jenkins
df75479069 hermes: close lane branch coverage gaps
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 15:03:59 -03:00