Skip to content

autoloop/20260425T014006Z/w5/2 2.b.5 non editable gateway progress commentary send fallba - #6

Merged
XelHaku merged 3 commits into
mainfrom
autoloop/20260425T014006Z/w5/2-2.b.5-non-editable-gateway-progress-commentary-send-fallba
Apr 25, 2026
Merged

autoloop/20260425T014006Z/w5/2 2.b.5 non editable gateway progress commentary send fallba#6
XelHaku merged 3 commits into
mainfrom
autoloop/20260425T014006Z/w5/2-2.b.5-non-editable-gateway-progress-commentary-send-fallba

Conversation

@XelHaku

@XelHaku XelHaku commented Apr 25, 2026

Copy link
Copy Markdown
Collaborator
  • ci(www): deploy gormes.ai to Cloudflare Pages
  • feat(progress): add RowHealth schema for autoloop
  • gateway: add non-editable send fallback

XelHaku and others added 3 commits April 24, 2026 20:12
Mirrors deploy-gormes-docs.yml for the landing site at gormes.ai.
The Go-rendered landing page had no CI deploy, so the operations-
first rewrite + favicons + OG/Twitter tags shipped to docs.gormes.ai
but never reached the actual gormes.ai surface — production was
still serving the pre-rewrite "Same Hermes Brain" copy with no
favicon.

Pipeline:
  1. Refresh internal/site/data/{benchmarks,progress}.json from the
     canonical sources at the repo root and under docs/. The local
     Makefile has the same rule, but its mtime check no-ops on
     fresh CI checkouts where source and target share a timestamp.
  2. make build → CGO_ENABLED=0 static binary.
  3. ./bin/www-gormes export --out dist → dist/{index.html,
     install.{sh,ps1,cmd}, static/*}.
  4. Verify dist/ has the expected artifacts and dist/index.html
     contains the new headline, favicon link, og:image, and is
     free of the old "Same Hermes Brain" / "Why a Go layer matters"
     tokens — same defense in depth as the docs workflow.
  5. wrangler pages project create gormes-www (idempotent) → pages
     deploy → attach gormes.ai and www.gormes.ai to the project.

Triggers on push to main with changes under www.gormes.ai/**, the
canonical benchmarks.json + progress.json (which flow into the
embedded data), the install scripts (mirrored under
www.gormes.ai/internal/site/installers/), and the workflow itself.
workflow_dispatch is also enabled for manual redeploys.

Reuses the same CLOUDFLARE_API_TOKEN + CLOUDFLARE_ACCOUNT_ID secrets
as deploy-gormes-docs.yml.

Project name 'gormes-www' is a placeholder mirroring 'gormes-docs'.
If the existing Cloudflare Pages project bound to gormes.ai uses a
different name, update --project-name= in the deploy + domain-
attach steps.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@XelHaku
XelHaku merged commit 76dc4dc into main Apr 25, 2026
@XelHaku
XelHaku deleted the autoloop/20260425T014006Z/w5/2-2.b.5-non-editable-gateway-progress-commentary-send-fallba branch April 25, 2026 07:48
XelHaku added a commit that referenced this pull request Apr 25, 2026
Implements items #6 and #10 from docs/cmd-loops-improvements.md.

#6 — Doctor actually diagnoses drift:
  * planner-loop doctor now also: parses+validates progress.json,
    verifies PLANNER_TRIGGERS_PATH is writable, and emits an advisory
    warning when the latest health_updated event is older than
    2× PLANNER_INTERVAL (planner ledger) or 1h (builder ledger).
  * builder-loop doctor introduced (was absent) with the same
    progress.json + triggers writability + builder-loop drift checks.
  * Drift warnings are advisory (exit 0) so doctor remains
    automation-safe; only hard preconditions (missing progress, parse
    failure, unwritable triggers path) fail the check.

#10 — Table-driven progress write:
  * Replaced the nine near-identical rewriteProgressMarker blocks with
    a []progressMarker table driven by one loop. Adding a new marker
    is now appending one struct entry instead of editing four
    boilerplate lines.

Helper extraction:
  * latestLedgerEventTime + driftWarning + triggerPathWritable are
    duplicated in both binaries for now (no internal/cmdcommon yet).
    Item #11 will collapse them once the cliDeps refactor lands.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
XelHaku added a commit that referenced this pull request May 8, 2026
`gormes update` now checks the on-disk config schema version after the
web build step. When the version is outdated:

- With --yes: auto-applies via internal/config.MigrateConfigFile and
  emits ✓ update_config_migrate_completed.
- Without --yes: emits ⚠ update_config_migrate_needed advisory pointing
  the operator at `gormes config migrate` (or rerun with --yes).

When the config is already current, no evidence is emitted (silent
default — no nag on every update). Best-effort: errors from either the
check or migrate seam emit ✗ update_config_migrate_failed but never
fail the overall update.

What's added:

- internal/cli ConfigCheckRunner + ConfigMigrateRunner seam types
  (parallel to SkillSyncRunner / WebBuildRunner pattern; decoupled from
  internal/config so the lifecycle stays import-free).
- internal/cli ConfigVersionResult struct mirroring CheckReport.
- 3 new evidence kinds:
  - update_config_migrate_completed → ✓ (auto-applied)
  - update_config_migrate_needed    → ⚠ (operator action required)
  - update_config_migrate_failed    → ✗ (check or migrate error)
- internal/cli emitConfigMigrate helper with 7-branch decision table
  documented inline. Runs after web build, before gateway restart.
- cmd/gormes default adapters wrap internal/config.Check and
  internal/config.MigrateConfigFile into the seam shape.
- updateGlyphAndColor extended: `*_needed` suffix → ⚠ (yellow,
  operator-action-required category).

Tests:

- internal/cli/update_lifecycle_config_migrate_test.go (6 lifecycle tests):
  - NilConfigCheck silent default
  - ConfigAlreadyCurrent silent (no nag)
  - ConfigOutdatedWithoutYes emits needed + does NOT call migrate
  - ConfigOutdatedWithYes auto-applies + emits completed
  - ConfigMigrateError emits failed + update still succeeds
  - ConfigCheckError emits failed + does NOT attempt migrate
- cmd/gormes/update_progress_ui_test.go ConfigMigrateGlyphs verifies
  ✓/⚠/✗ for the 3 new kinds.

Implements row #6 from the gormes-update parity batch (audit agent's
recommended order). Reuses the structured progress UX from c83d10e
and the silent-default + best-effort patterns established by 56efc40
(backup), f30f72c (skill sync), and fa4a10d (web build).

Owned divergence vs Hermes' richer prompt:

- Interactive y/n at the prompt is intentionally not in this slice.
  Hermes' interactive flow would require a stdin reader seam and more
  branching. The slice ships --yes auto-apply + advisory-when-not, and
  the operator interactive path remains owned by the existing
  `gormes config migrate` command.
- Per-bucket missing_env / missing_config counts (Hermes' "⚠️ N
  required setting(s) need configuration") are not in this slice.
  CheckReport's Issues field carries them but the slice surfaces only
  version-skew. Detailed breakdown is a follow-up if operators ask.

Audit batch progress: 5 of 6 update-parity rows shipped (structured
UX, pre-update backup, skill sync, web build, config migrate).
Remaining: goncho sync (#4) — deferred until internal/goncho gains a
profile-sync surface.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
XelHaku added a commit that referenced this pull request May 9, 2026
* feat: add OpenCode backend support to cron/loop scripts

* feat: add Telegram thread-not-found fallback + send retry safety

Add retry logic to SendThread/SendThreadReply/SendThreadChatAction:
- Thread-not-found errors retry once without message_thread_id
- Non-thread BadRequest fails immediately (no retry)
- TimedOut never retries (avoids duplicate messages)
- Transient NetworkErrors bounded to 3 attempts
- New helpers: isThreadNotFoundError, isTimedOutError, isTransientNetworkError

TDD: 15 new tests across thread_fallback_test.go + send_retry_test.go.
Matches Hermes b816fd4e2 _is_thread_not_found_error retry loop semantics.

* docs: update progress for Telegram forum thread fallback row completion

Row status: planned→complete. Planner pass: sharpened contract_status
draft→validated, updated ready_when/not_ready_when to reflect completed
thread-aware send seam dependency. Regenerated all progress-derived docs
via go run ./cmd/progress write. 720 complete, 49 planned remaining.

* fix: update Phase 2 test expectations from in_progress to complete

Phase 2 has all 21/21 subphases and 73 routing/context rows shipped.
Tests TestLoad_RealFile and TestLoad_RealFile_Phase2ExecutionQueue
were asserting in_progress for Phase 2 and 2.B.5 respectively; both
are now complete. This restores CI green.

* feat: add browser session inactivity cleanup with configurable timeout

Port Hermes BROWSER_SESSION_INACTIVITY_TIMEOUT (default 300s) to Gormes as a
BrowserSessionTracker with background reap goroutine. TDD delivers six fixtures:
idle reaping, active preservation, reap-interval ticking, env-var override,
nil-backend safety, and close-failure evidence recording.

* docs: add browser inactivity cleanup row to Phase 5.C progress

All-topic parity sweep (CLI/TUI, provider/auth, gateway/channels, tools,
sessions/memory/Goncho, install/runtime, browser automation, docs/public,
release/operator) identified browser inactivity cleanup as the highest-impact
source-backed gap. Added P2 row under 5.C with contract, write_scope,
test_commands, and Hermes upstream ref. Regenerated all progress surfaces.

* feat: add SkillValidator ForceLoad, async, and canary execution support

Completes Phase 6.L 'Skill validation on load with execution proof' (P2):
- ForceLoad field on SkillValidator persists validation errors while
  allowing operator override.
- ValidateAsync and ValidateCanaryAsync run in background goroutines
  with channel-based result delivery (<500ms guarantee).
- ValidateCanary uses language-specific minimal safe inputs (python,
  bash, javascript) to catch gross syntax/import errors quickly.
- ForceLoadMsg formats human-readable force-load explanations.
- 13 new tests covering: force-load on/off, async success/failure,
  canary success/failure, fallback for unknown languages, duration,
  and ForceLoadMsg content verification.

* docs: complete Phase 6.L skill validation row and close deprecated umbrellas

Parity sweep (2026-05-07) findings applied:
- Phase 6.L 'Skill validation on load with execution proof' (P2) marked complete.
- Phase 5.O deprecated CLI profile umbrella marked complete (replaced by siblings).
- Phase 5.D Multimodal in/out umbrella closed (all children complete).
- Regenerated progress-driven docs and landing site progress data.

* chore: vendor hermes-agent as a git submodule on origin/main

Convert the in-repo hermes-agent reference checkout from a gitignored side-
clone into a tracked submodule pinned to NousResearch/hermes-agent main.

Local mods on the previous side-clone (Karpathy guardrails template added to
AGENTS.md/CLAUDE.md by onboarding tooling) were not upstream work and were
backed up to /tmp before resetting. The submodule now matches upstream HEAD
exactly so the gormes-hermes-parity sweep classifies against current truth
instead of a 3-day-stale checkout.

* docs: refresh hermes-agent submodule at parity sweep start

Teach gormes-hermes-parity to update the in-repo hermes-agent submodule to
upstream main HEAD before reading parity references, and to pass that bumped
sha through to gormes-git when builder rows land. Falls back to fetch+ff-only
for legacy clones, no-op if absent. Prevents stale-baseline classification.

* chore: drop hermes-agent from .gitignore now that it is a submodule

Remove the gitignore entry that hid the previous side-clone, and replace it
with a marker comment pointing to .gitmodules. Without this, the submodule
worktree would still be ignored at the parent level and confuse contributor
tooling.

* docs: complete Phase 6.L code executor and dependency resolver rows

Mark 'Skill code execution runtime' (P2) and 'Skill dependency resolution
and composition' (P3) as complete with implementation evidence and passing
test verification. Both rows had full Go implementations on disk
(internal/skills/code_executor.go, dependency_resolver.go) with passing
tests but were incorrectly marked as planned in progress.json.

Evidence: 4 code executor tests (success, error exit, empty code, default
timeout) + 5 dependency resolver/composer tests (linear, circular, missing,
compose, validate chain) all pass. Regenerated progress surfaces.

* docs: add 6 builder-ready rows from Hermes v0.12.0 + v0.13.0 parity sweep

gormes-planner pass over Hermes releases v2026.4.8..v2026.5.7
(v0.8.0..v0.13.0). Adds six progress.json rows materializing the
highest-leverage gaps surfaced by the sweep, with full builder contracts
(contract, contract_status, slice_size, execution_owner, trust_class,
degraded_mode, fixture, source_refs, ready_when, not_ready_when, blocked_by,
unblocks, acceptance, write_scope, test_commands, done_signal, note,
provenance):

- 5.Q  P2/medium  Provider client lazy-init for TUI cold-start budget
                  (v0.12.0 #17046 lead of the ~57% cold-start cut family)
- 5.I  P2/small   Plugin lifecycle hook: transform_llm_output
                  (v0.13.0 #21235)
- 5.D  P2/medium  Native video_analyze tool contract (v0.13.0 #19301)
- 5.M  P1/medium  Kanban worker heartbeat, reclaim, and zombie detection
                  (v0.13.0 #21183, #21214, #20188, #20410)
- 7.E  P2/small   Google Chat shared-chassis platform adapter seam
                  (v0.13.0 #21306, #21331)
- 5.J  P0/small   Auth state TOCTOU close + redaction default-on parity
                  (v0.13.0 #21193, #21194, #21241; reverts v0.12.0 #16794)

Each row's `unblocks` field names sibling work explicitly deferred from this
pass (cold-start mtime cache / tool memoize / pattern precompile,
pre_gateway_dispatch / pre_approval / duration_ms hooks, Gemini video
transport, Google Chat transport binding, etc.) so future planner picks have
an obvious next step. Provenance is recorded as the schema struct
(origin_type=upstream); upstream_ref left empty pending symbol-level mapping
when each row is picked up by a builder.

Derived surfaces regenerated by `go run ./cmd/progress write`. Validated by
`go run ./cmd/progress validate` (7 phases ok) and
`go test ./internal/progress -count=1` (ok).

* docs: add 7 missing upstream Hermes doc mirrors for mirrored coverage parity

* docs: add Phase 8 (Reputation & Publication) + 12-month success plan

Strategic pivot for Gormes from "Hermes in Go" parity chase to
TrebuchetDynamics reputation play. The methodology is the product;
Hermes-parity is the receipt that proves the methodology works.

New strategy doc: docs/content/building-gormes/strategy/success-plan.md
- North Star: "TrebuchetDynamics is the team that figured out how to
  autonomously port large Python projects to Go in production. Gormes is
  the receipt."
- Quarterly roadmap (Q1 foundation → Q2 toolkit OSS → Q3 sharp v1.0 →
  Q4 compound).
- 30-day action sprint (TD blog, README rewrite, writeup #1 draft, sharp
  differentiator decision, $/iteration cost telemetry).
- Operating principles: publication cadence > commit cadence; solo devs
  win on opinion not scope; the loop is the product; cost discipline.
- Reputation metrics scoreboard replacing row-count vanity metrics.
- Risk register including publication paralysis, identity drift, loop
  spend without learning.

New progress.json Phase 8 with 7 subphases and 10 builder-tractable rows
(all gormes-owned, provenance.origin_type=gormes):

- 8.A Publication Infrastructure
    P1 TD engineering blog scaffolded and live
    P3 TD social presence connected to blog feed
- 8.B Repository Messaging
    P1 README rewrite to methodology-first positioning
    P2 gormes.ai landing page positioning audit
- 8.C Engineering Writeups
    P1 Engineering writeup #1: autonomous Hermes-porting loop
- 8.D Sharp v1.0
    P0 Sharp v1.0 differentiator decision
    P1 Single-binary cross-platform release pipeline
- 8.E Toolkit Extraction
    P2 Agentic-porting-kit repo scaffold
- 8.F Cost Discipline & Loop Economics
    P1 Loop $/iteration cost metric in status file
- 8.G Community & External Contributions
    P3 Built-with-Gormes page scaffold

Each row carries the full builder contract (contract, contract_status,
slice_size, execution_owner, trust_class, degraded_mode, fixture,
source_refs, ready_when, not_ready_when, blocked_by, unblocks,
acceptance, write_scope, test_commands or no_test_required, done_signal,
note, provenance). Strategy-only rows that need operator taste (writeup
voice, differentiator decision) explicitly carry no_test_required with a
human-checked acceptance contract.

Validated: go run ./cmd/progress write/validate (8 phases ok),
go test ./internal/progress (ok), git diff --check.

* docs: rewrite landing page hero + add methodology section (8.B materialization)

Materializes progress.json row 8.B "gormes.ai landing page positioning audit"
under the methodology-first North Star captured in success-plan.md.

Hero changes:
- New title: "Hermes-Compatible AI Agent Runtime in One Go Binary".
- New headline: "AI agent runtime in one Go binary." (outcome-first per
  landing-quality-rubric).
- New sub-lines lead with Hermes-ecosystem compatibility, then introduce
  the autonomous engineering loop as the trust play.
- New kicker: "OPEN SOURCE · MIT LICENSE · AUTONOMOUSLY PORTED FROM HERMES".
- Secondary CTA changes from "View on GitHub" to "See how it is built"
  pointing at the new methodology section.
- Proof strip adds "Autonomous porting loop ships daily".

New section: HOW IT IS BUILT (#methodology), placed between hero and
install. Contains:
- A measured-loop-output metrics row (rows shipped, code base, binary)
  bound to benchmarks.json so it stays current.
- Four pillar cards: validation-gated commits, progress.json as system
  of record, Hermes is the parity oracle, reusable porting toolkit.
- Link to /building-gormes/architecture_plan/.

Trust posture:
- Adds "Every autonomous-loop commit passes a validation gate (go test,
  progress validate, git diff --check) before landing" to the trust list.

Top nav restructured:
- "Docs" replaced with "How it is built" (jumps to methodology).
- "Install" added as a top-level anchor.

Playwright tests updated to match the new strings; mobile-overflow tests
updated for the new headline. All 5 tests pass at iPhone SE through
iPhone Plus widths.

Validated:
  npm run build              -> ok
  npm run test:e2e           -> 5 passed
  go run ./cmd/progress      -> validated 8 phases
  git diff --cached --check  -> ok

Other dirty paths in the worktree (kanban .go files, progress.json
status updates, derived builder-loop docs) belong to the autonomous
loop's in-flight work on row 5.M and will land via the loop's own
gormes-git step.

* Release v0.1.06 (v2026.5.7)

The Pivot Release. Adopts the Hermes-style dual versioning taxonomy
(canonical semver tag v0.1.06 paired with date alias v2026.5.7),
captures a 12-month methodology-first strategy, and stands up Phase 8
in progress.json.

Highlights
- Methodology-first repositioning: success-plan.md is the new North
  Star ("TrebuchetDynamics is the team that figured out how to
  autonomously port large Python projects to Go in production. Gormes
  is the receipt").
- Phase 8 (Reputation & Publication) added with seven subphases
  (8.A-8.G) and ten gormes-owned builder-ready rows.
- Six Hermes-parity rows materialized from upstream v2026.4.30 +
  v2026.5.7 sweep (cold-start lazy-init, transform_llm_output hook,
  video_analyze tool, Kanban worker heartbeat, Google Chat adapter,
  auth TOCTOU + redaction default-on P0).
- Landing page rewrite: outcome-first hero, new methodology section
  with live loop-output metrics, four pillar cards.
- hermes-agent now tracked as git submodule pinned to upstream
  NousResearch/hermes-agent main (sha 7e2af0c2e).
- opencode/deepseek-v4-pro builder backend added alongside codexu;
  GORMES_BUILDER_BACKEND switch on the cron loop.
- Phase 6.L code executor and dependency resolver rows complete.

Versioning Taxonomy
- Canonical git tag: v0.1.06 (semver; gates with cmd/gormes/version.go).
- Date alias: v2026.5.7 (Hermes-style vYYYY.M.D, surfaced in release
  notes filename header, GitHub release title, and CHANGELOG entry).
- A future row migrates the canonical git tag to the date form once
  the release workflow extracts version from cmd/gormes/version.go
  independently of the tag string.

Validation
  go test ./... -count=1            ok (full suite)
  go run ./cmd/progress validate    validated 8 phases
  git diff --check                  ok
  landing build                     ok
  landing e2e                       5 passed (homepage + 4 mobile vps)

Files
- cmd/gormes/version.go             0.1.05 -> 0.1.06
- CHANGELOG.md                      [0.1.06] - 2026-05-07 entry
- RELEASE_v0.1.06.md                Hermes-style release notes (new)
- webpages/landing/src/data/release.json
                                    version bump for landing surface

* docs: add gormes-install skill — sandbox-isolated install testing

New repo-local skill at docs/development-skills/gormes-install/.
Symlinked into .agents/skills/, .claude/skills/, .codex/skills/.

Purpose
- Test and validate Gormes install + setup paths against a real
  release. Surface install-time issues that pure CI cannot catch
  (PATH leaks, shell-rc edits, symlink hijacking, misleading status
  messages, hidden side effects on existing installs).

Authored from a real test pass against v0.1.06 (the release just
cut). Three open install issues filed in references/known-issues.md
from that pass:

- iso-bin-hijack — sandbox install hijacks production
  ~/.local/bin/gormes symlink despite GORMES_BIN_DIR being set to a
  sandbox path. Active-PATH-command update should respect sandbox
  prefix.
- iso-shellrc-leak — sandbox install permanently edits ~/.bashrc and
  ~/.profile with a /tmp/-rooted PATH entry. After /tmp is reaped,
  every login shell has a dangling PATH. No installer-emitted
  cleanup path.
- msg-systemd-fiction — install transcript unconditionally announces
  "systemd user service installed" even when no service file is
  written and `systemctl --user list-unit-files 'gormes*'` reports
  zero units.

All three need follow-up rows under Phase 5.P (installer) or 5.O
(CLI surfaces) and TDD coverage via gormes-tdd-slice. This skill
finds and documents; it does not patch.

Skill structure
- SKILL.md       — mission, when-to-use table, hard constraints,
                   nine-step workflow, validation, final-report.
- references/known-issues.md — living register with format spec and
                   coverage gaps worth closing (uninstall, --local,
                   Termux, WSL2, root install, install.ps1, fresh
                   machine without Go/git, curl|bash flow,
                   upgrade-in-place with non-default config).
- references/test-recipes.md — copy-paste recipes for sandbox setup,
                   pre/post-state capture, dry-run, real install,
                   diff, functional smoke, production restoration,
                   uninstall, branch/tag pass.

Validated
  python3 quick_validate.py docs/development-skills/gormes-install   ok
  go run ./cmd/progress validate                                     8 phases ok
  git diff --check                                                   ok

* chore: update hermes-agent submodule pointer

* docs: file 3 install issues from gormes-install pass as 5.P planner rows

Materializes the three open install issues found by the gormes-install
sandbox pass on 2026-05-07 against install.sh@08706c6d3 (the v0.1.06
release-cut commit). Each row carries the full builder contract and
references the skill's known-issues register as the source of truth.

Rows added under Phase 5.P (Docker / Packaging):

- P1/small  Install isolation: GORMES_BIN_DIR is an authoritative
            sandbox boundary
            (fixes iso-bin-hijack — sandbox install hijacks production
            ~/.local/bin/gormes symlink despite GORMES_BIN_DIR being
            set to a sandbox path)

- P2/small  Install isolation: skip shell-rc PATH write when bin dir
            is under /tmp
            (fixes iso-shellrc-leak — sandbox install permanently
            writes PATH lines to ~/.bashrc / ~/.profile from sandbox
            paths that dangle when /tmp is reaped)
            blocked_by: iso-bin-hijack row (shares sandbox-detection
                       helper)

- P2/small  Install transcript: only print systemd block when unit
            file actually written
            (fixes msg-systemd-fiction — install transcript
            unconditionally announces "systemd user service installed"
            even when no unit file exists)

All three rows are provenance.origin_type=gormes (install.sh is
gormes-owned, not a Hermes port). Each acceptance contract pins the
fix to a Go integration test under internal/installtest/ that execs
install.sh in a temp HOME with isolated PATH and asserts on
post-state diffs. Tests must shim systemctl so outcomes are
deterministic regardless of host systemd availability.

Validated:
  go run ./cmd/progress write     ok
  go run ./cmd/progress validate  validated 8 phases
  go test ./internal/progress     ok 0.736s
  git diff --check                ok

* docs: retract msg-systemd-fiction install issue (verification error)

When investigating row 5.P "Install transcript: only print systemd
block when unit file actually written" for a TDD fix, found the
underlying bug does not exist. install.sh's gating
  if has systemctl && systemctl --user >/dev/null 2>&1; then
    install_systemd_user_service
  fi
already correctly conditions the unit-file write and the transcript
block on systemd availability. The unit IS written
(~/.config/systemd/user/gormes-gateway.service exists and
`systemctl --user is-enabled gormes-gateway` reports `enabled`).

Root cause of the original false positive: the gormes-install pass
on 2026-05-07T17:27Z verified "no systemd unit installed" by running
`systemctl --user list-unit-files 'gormes*'` and reading
"0 unit files listed" as ground truth. That command can transiently
miss a unit immediately after `daemon-reload` until systemd re-scans.
A single negative result is not authoritative.

Changes
- Withdraw progress.json row 5.P "Install transcript: only print
  systemd block when unit file actually written" (P2). 5.P item count
  goes from 9 back to 8. The two remaining install-isolation rows
  (iso-bin-hijack P1, iso-shellrc-leak P2) are real and stay.
- Move msg-systemd-fiction in
  webpages/docs/development-skills/gormes-install/references/known-issues.md
  from "Open Issues" to a new "Withdrawn (False Positives)" section
  with the full retraction context, root cause, and the verification
  lesson learned.
- Tighten the gormes-install SKILL.md pre-state capture step to
  require THREE independent signals when checking systemd unit
  presence (file existence + is-enabled + list-unit-files without a
  glob pattern), so future passes do not repeat the misdiagnosis.
- Update test-recipes.md pre/post-state snapshot scripts to use the
  three-signal pattern instead of the misleading single
  `list-unit-files 'gormes*'` query.

Validated:
  python3 quick_validate.py gormes-install                  Skill is valid!
  go run ./cmd/progress validate                            validated 8 phases
  go test ./internal/progress -count=1                      ok 0.770s
  git diff --check                                          ok

Trust-but-verify lesson preserved in the skill's known-issues.md so
future install passes start from corrected ground truth, not from
the original mistaken evidence.

* fix(install): respect GORMES_BIN_DIR/GORMES_PREFIX as sandbox boundary

Closes iso-bin-hijack from gormes-install pass on 2026-05-07.

Before: install.sh's update_active_command() ran unconditionally after
publishing the binary. It walked `which -a gormes` plus ~/.local/bin/gormes
and ~/go/bin/gormes, replacing every other gormes symlink it found on
PATH with one pointing at the just-installed sandbox build. After /tmp
reaping, the production ~/.local/bin/gormes symlink dangled.

After: when GORMES_BIN_DIR or GORMES_PREFIX is set, the operator has
declared a sandbox boundary. update_active_command() now early-returns
with `skipping active PATH command update (sandbox bin dir set ...)`.
print_install_plan_body() and print_verbose_plan() surface the decision
as `update_active_path_command: skipped|yes` so dry-run plans expose the
choice.

Test: internal/installtest/iso_bin_dir_test.go covers four cases via
fast `--dry-run` plan inspection (no git clone, no go build):
GORMES_BIN_DIR skips, GORMES_PREFIX skips, default still updates,
verbose surfaces the reason. End-to-end manual verification on
2026-05-07 confirmed a real sandbox install with GORMES_BIN_DIR set
leaves the production ~/.local/bin/gormes symlink target unchanged.

Implements progress.json row 5.P "Install isolation: GORMES_BIN_DIR is
an authoritative sandbox boundary". known-issues.md entry updated to
fixed-in-2026-05-07.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(ci): align deploy-gormes-www asserts with methodology-first landing

The Deploy gormes.ai workflow's post-build verify step was failing on
the v0.1.06 release commit (run 25511450496) because it asserted hero
strings from before the 8.B landing rewrite. The page builds fine; only
the asserts were stale.

Updates:
- Replace stale positive asserts with current strings:
  "AI agent runtime in one Go binary." (hero h1)
  "An autonomous engineering loop ports Hermes to Go, every day." (methodology h2)
- Add new positive asserts that lock in the 8.B pivot so a regression
  to the old pre-pivot hero gets caught:
  "HOW IT IS BUILT" (methodology kicker)
  "Validated rows shipped" (methodology metrics block)
- Move the now-stale hero string to the negative-assert block:
  "Run AI agents from one Go binary."
  "Gormes runs local agent sessions, provider turns, memory, dashboards, and chat gateways from one Go binary."

Also picks up the website install.sh mirror refresh (now includes the
iso-bin-hijack sandbox-bin-dir fix from a7116c11e) and the daily
benchmarks rebuild.

Local verification: every positive asserts grep-matches the current
dist/index.html; every negative asserts grep-misses it.

Closes the red Deploy gormes.ai run from the v0.1.06 merge commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(install): close iso-shellrc-leak and iso-systemd-hijack

Two more sandbox-isolation bugs discovered by the gormes-install pass on
2026-05-07. Both have the same shape as iso-bin-hijack and the same fix
shape: gate on the existing sandbox_bin_dir_set helper.

iso-shellrc-leak (P2):
Before: ensure_path_in_shell_config() unconditionally appended
"export PATH=/tmp/gormes-install-test/<ts>/bin:$PATH" lines to ~/.bashrc,
~/.profile, ~/.zshrc, or fish config. After /tmp reaping, every login
shell carried a dangling PATH entry the operator had to sed out by hand.

After: ensure_path_in_shell_config() early-returns with
PATH_CONFIG_RESULT=sandbox_skipped when sandbox_bin_dir_set is true,
after exporting the bin dir into the current install run's PATH (so
downstream verification still works). Logs the boundary decision.

iso-systemd-hijack (P0):
Before: print_service_instructions() unconditionally rewrote the
production user systemd unit ~/.config/systemd/user/gormes-gateway.service
with ExecStart pointing at /tmp/gormes-install-test/<ts>/home/bin/gormes
and Environment=GORMES_HOME=/tmp/gormes-install-test/<ts>/home. After
the next reboot or /tmp reap, the operator's gormes-gateway service
fails to start because its binary path no longer exists. Worse than
iso-bin-hijack: failure is invisible until the operator tries to use
the gateway. Same surface on macOS via the launchd plist.

After: print_service_instructions() early-returns when
sandbox_bin_dir_set is true, with a log line naming both
~/.config/systemd/user/ and ~/Library/LaunchAgents/. Plan emitters
surface install_system_service: skipped|yes.

Plan output (print_install_plan_body and print_verbose_plan) now
exposes all three boundary decisions:
- update_active_path_command: skipped|yes
- edit_shell_rc_files: skipped|yes
- install_system_service: skipped|yes

Tests: 6 new dry-run plan tests across iso_shellrc_test.go and
iso_systemd_dir_test.go (skipped GORMES_BIN_DIR, skipped GORMES_PREFIX,
default-yes regression fence for each surface).

End-to-end verification on 2026-05-07: real sandbox install with
GORMES_BIN_DIR set left ~/.bashrc and ~/.profile gormes-line counts
byte-identical and ~/.config/systemd/user/gormes-gateway.service sha256
byte-identical.

Implements progress.json rows:
- 5.P "Install isolation: skip shell-rc PATH write when bin dir is under /tmp" (note: shipped fix uses sandbox_bin_dir_set boundary, not the originally-planned bin-dir-under-/tmp heuristic)
- 5.P "Install isolation: skip system service install when sandbox bin dir is set" (new row, P0)

known-issues.md entries updated to fixed-in-2026-05-07.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: add loop $/iteration cost metric with --cost-report subcommand

* docs: unblock P0 5.J TOCTOU row, complete P1 8.F cost metric, regenerate progress surfaces

* chore: gitignore scheduled tasks lock file

* feat(security): close auth.json TOCTOU window, add redaction default-on contract

Use tools.AtomicReplace (tempfile -> fsync -> rename) in
writeCredentialPoolAuthStore instead of raw os.WriteFile to
prevent concurrent readers from observing partially-written
auth.json. New internal/redaction package provides
RedactionConfig with default-on contract (Enabled:true) and
explicit operator opt-out via Enabled:false.

* docs: mark P0 TOCTOU row complete, sharpen Docker umbrella, add gateway auto-resume row

Parity sweep + planner pass:
- P0 5.J TOCTOU row: contract_status validated, then marked complete
- 5.B Docker: split inventory umbrella into concrete P1 builder row
- 5.N: new P2 gateway auto-resume row from Hermes v0.13.0
- Regenerate all progress-derived docs and site data

* feat: gateway auto-resume on restart recovers interrupted sessions

TDD landed autoResumePendingSessions in Manager.Run: on gateway
startup, scans session metadata for ResumePending sessions, injects
synthetic empty-text submit events for sessions with registered
channels, and marks orphaned sessions as non-resumable with
adapter_not_ready evidence.

Five fixture-backed tests prove: interrupted session recovery,
orphaned session terminated, startup resilience, channel-neutral
behavior (telegram/slack/discord), and session-boundary hook
preservation. Added NonResumableAdapterNotReady constant and
ManagerConfig.SkipAutoResume for test isolation.

* docs: parity sweep cleanup + gateway auto-resume row complete

- Cleaned 3 stale needs_human planner verdicts on completed rows
- Added P2 Brave Search + DDGS web search provider parity row (5.C)
- Marked Gateway auto-resume on restart complete with evidence
- Regenerated all derived progress surfaces

* chore: update hermes-agent submodule pointer

* feat(kanban): worker heartbeat, zombie reclaim, unified failure counter, auto-block, darwin detection

Implement Kanban worker heartbeat extension (extends claim_expires by
heartbeatTTL), stale-heartbeat zombie detection and reclaim with retry
budget, unified failure counter across spawn/timeout/crash outcomes,
auto-block on incomplete worker exit, and darwin PID-state zombie
detection via syscall.Kill (cross-platform stub on other GOOS).

TDD coverage: 8 acceptance tests over in-memory SQLite with fake clock.
Mirrors Hermes v0.13.0 PRs #21183, #21214, #20188, #20410.

* docs: mark kanban heartbeat row complete, regenerate progress surfaces, bump hermes submodule

Mark P1 'Kanban worker heartbeat, reclaim, and zombie detection' row
complete with TDD evidence. Regenerate all derived progress docs via
'go run ./cmd/progress write'. Bump hermes-agent submodule to
cff821e2d (upstream main from parity sweep).

* feat(tools): Docker execution backend with mount policy, env filtering, and fake-client tests

* docs: regenerate progress surfaces from parity sweep and Docker row, bump hermes submodule

* feat(install): default to release binary fetch instead of source build

curl https://gormes.ai/install.sh | bash now completes in ~2 seconds via
signed binary fetch from GitHub Releases instead of ~2-5 minutes via Go
toolchain download + git clone + go build. The slow source-build path
was the only install method, even though v0.1.06 already publishes 18
release assets (linux/darwin/windows × amd64/arm64 .tar.gz + .sha256 +
SBOM + build provenance attestations).

What's added:

- release_platform_arch() — maps uname -s/-m to a release-asset slug
  (linux-amd64, linux-arm64, darwin-amd64, darwin-arm64). Returns
  empty for unsupported platforms.

- decide_install_method() — 5-rule precedence picks binary-fetch or
  source-build at parse time so plan emitters can declare it:
  1. --local → source-build (operator wants their working tree)
  2. --from-source / GORMES_INSTALL_FROM_SOURCE=1 → source-build
  3. --branch != main → source-build (release binaries are only
     published from main)
  4. unsupported platform → source-build (graceful)
  5. otherwise → binary-fetch (the new fast path)

- fetch_release_binary() — GitHub API resolves the latest tag, curl
  or wget downloads gormes-<v>-<arch>.tar.gz + .sha256, sha256sum or
  shasum -a 256 verifies, tar -xzf extracts, mv installs to
  managed_bin_dir. Any failure (network, missing asset, hash
  mismatch, missing tools) triggers a clear log line and falls back
  to source-build so installs still succeed in restricted environments.

- Plan emitters now declare:
  - install_method: binary-fetch (linux-amd64 from latest release...)
    OR source-build (--from-source flag set | unsupported platform | ...)
  - source: github releases | managed git checkout | <local path>
  Verbose plan also emits source_mode, release_arch, release_api so
  operators can audit the network call before running real install.

End-to-end verification on Linux/amd64: a fresh sandbox install with
GORMES_BIN_DIR set completed in 1.832s wall, with Go never invoked
(grep -c 'Checking Go' = 0), exit=0, and `gormes version` reporting
0.1.06 from the published release.

Tests: 5 new dry-run plan tests in internal/installtest/install_method_test.go
covering the default binary-fetch path, --from-source override, env-var
override, non-default-branch fallback, and verbose plan details.

Implements progress.json row 5.P "Install: prefer pre-built release
binary over source build by default". Closes the binary-fetch coverage
gap noted in webpages/docs/development-skills/gormes-install/references/test-recipes.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(setup): prettify wizard with framed headers, screen clearing, and color

The fresh-install setup wizard now matches Hermes' UX polish: each section
clears the prior screen, renders a Hermes-style framed header, and uses
color/bold to highlight the active selection.

Source: 2026-05-07 fresh-Fedora install transcript from operator. The
operator-visible flow was previously a Spartan stack of plain-text prompts
and a 47-line provider list with no visual hierarchy.

What's added:

- internal/cli/wizard_ui.go — TTY-gated styling helpers:
  - ClearScreen(io.Writer) emits ANSI clear+home only on a TTY and only
    when GORMES_NO_CLEAR_SCREEN is not opted out.
  - Bold/Dim/Cyan/BrightCyan/Yellow/Green wrap text in ANSI escapes only
    when the writer is a TTY and NO_COLOR is not set (no-color.org).
  - PrintHeader emits a Hermes-style ╭──╮│ title │╰──╯ box with width
    clamp 32-60 cols.
  - PrintSectionDivider emits a 60-char dim horizontal rule.

- internal/cli/wizard_ui_test.go — 7 unit tests proving:
  - ClearScreen is a no-op on non-TTY writers (critical for piped
    transcripts and CI logs).
  - Color helpers pass through plain text on non-TTY and under NO_COLOR.
  - PrintHeader renders the framed title even without ANSI on non-TTY.
  - GORMES_NO_CLEAR_SCREEN opt-out blocks the screen clear.

Wired into:

- cmd/gormes/setup.go — runSetupFirstTimeChoice ("How would you like to
  set up Gormes?"), runSetupQuick, runSetupProviderSection,
  runSetupModelSection, printSetupTopLevelMenu all now ClearScreen +
  PrintHeader at section entry. Active menu option (the default) is
  rendered in bright-cyan ` → (●)` + bold label; inactive options use
  dim `(○)` markers.

- cmd/gormes/model.go — promptProviderChoice ClearScreen + PrintHeader
  ("Choose a provider") + colored markers: yellow `*` for the default
  provider, bright-cyan number + bold label, dim numbers + auth-type
  tag for the rest. The 47-provider list now visually scans top-down
  with the default provider standing out.

Existing test contract preserved: every Setup/Model test still asserts
on `strings.Contains` of the literal section names, and PrintHeader
emits the title verbatim within the box so those checks still pass.

Visually verified under `script -qfc` (faked TTY): screen clears
between sections, framed bright-cyan headers render with bold titles,
selection markers are colored, NO_COLOR=1 strips all escapes cleanly,
and pipe redirection (e.g., for log capture) emits plain text.

Note: this fix lands on main, but fresh `curl ... | bash` operators
will only see the prettified wizard after v0.1.07 cuts (install.sh's
binary-fetch path downloads the latest release binary, not main HEAD).
Operators can opt into source-build for immediate access via
`curl ... | bash --from-source` or GORMES_INSTALL_FROM_SOURCE=1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: add MCP channels_list tool with ChannelDirectoryProvider seam

Implements Hermes MCP channels_list tool (#21474 fix) in Go-native form:
- ChannelDirectoryProvider interface for reading platform channel data
- channelsListHandler reads from Platforms map, returns id/name/chat_id/enabled/platform per channel
- Platform filter support via optional 'platform' argument
- Degraded-mode error when directory unavailable
- Registered in RegisterDefaultTools alongside existing MCP tools

* docs: complete MCP channels_list row, elevate to P1, sharpen 4 umbrella rows

Parity sweep follow-up:
- MCP channels_list tool: status=complete with evidence
- Elevated priority from P2 to P1 (unblocked, validated contract)
- Added no_test_required to 4 umbrella rows: 61-tool registry port,
  Atomic checkpoints, 49-file CLI tree port, Config/profile/auth/setup
- Regenerated all progress-driven docs and site data

* feat: complete Brave Search + DDGS web search provider parity (P1 row closure)

- Add TestBraveProvider_DegradedWhenNoKey acceptance test
- Mark Brave Search + DDGS row complete in progress.json
- All acceptance criteria covered: Brave API search, DuckDuckGo search,
  backend matrix resolution, degraded-mode when key missing
- Implementation in internal/tools/web_tools.go as part of rows 5.C[6]/5.C[7]
- Reprioritized P2→P1 to match upstream Hermes v0.13.0 parity demand

* feat: complete Sandbox isolation depth selection row (P3) with config loader and TDD fixtures

* docs: mark Sandbox isolation depth selection complete, regenerate progress surfaces

* chore: track upstream hermes-agent (author map + UV_NO_CONFIG fix)

* feat(update): structured progress UX with banner, glyphs, and summary

`gormes update` previously emitted Spartan output that operators struggled
to scan:

    update branch: main
    update_check	checked update readiness for main

It now matches the Hermes-style cadence:

    ⚕ Updating Gormes Agent...

    update branch: main
    ℹ update_check	checked update readiness for main

    ✓ Update complete!

What's added:

- Bold `⚕ Updating Gormes Agent...` banner at the top so the operator
  immediately sees what's happening.
- Per-evidence glyph + color via `updateGlyphAndColor`:
  - `*_failed`, `*_error`         → ✗ (bold)
  - `*_unavailable`, `*_timeout`  → ⚠ (yellow)
  - `update_check`, `*_log_mirrored`, `update_not_managed_checkout` → ℹ (dim)
  - default                       → ✓ (green)
- Final `✓ Update complete!` (green+bold) on success or `✗ Update failed`
  (bold) on failure. Operator recovery still routes to stderr.
- Dim `update branch:` / `previous branch:` labels so the values stand out.

Backward compatibility: every UpdateEvidenceKind string still appears
verbatim in stdout so downstream parsers and existing tests that grep for
`update_*` kinds keep working. Verified by
TestUpdateCommand_PreservesEvidenceKindsForMachineReadability.

Sandbox safety: the cli.* color/style helpers no-op on non-TTY writers
(bytes.Buffer in tests, pipes/redirects in real use) and under NO_COLOR=1
so transcript captures and CI logs stay clean. Verified by
TestUpdateCommand_NoColorStripsAnsi.

6 new tests in cmd/gormes/update_progress_ui_test.go cover banner ordering,
success summary, failure summary + recovery routing, evidence-kind
backward compat, glyph-by-class mapping, and NO_COLOR strip.

Implements the first row from the gormes-update parity batch (the audit
agent's recommended #1, "structured progress UX"). Reuses the wizard_ui.go
helpers shipped in e7f492211 — same vocabulary across `gormes setup` and
`gormes update`. Unblocks the 5 remaining gormes-update parity rows
(skill sync, profile fan-out, web build, pre-update backup, config
migration prompt) which all build on this transcript shape.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(update): wire pre-update backup policy decision into structured progress

`gormes update` now exposes the operator-explicit pre-update backup choice
through the structured progress UX. The actual backup writer (zip
creation, size/duration reporting, retention) is the next slice; this row
ships the FLAG/DECISION surface so the wiring lands first.

What's added:

- `--backup` flag opts into a single-run pre-update backup of ~/.gormes.
- `--no-backup` flag force-skips the backup; beats both `--backup` and
  the future config-level opt-in (matches Hermes' precedence).
- `UpdateLifecycleOptions.Backup`, `NoBackup`, `BackupConfigEnabled`
  threaded through from cobra to RunUpdateLifecycle.
- New evidence kinds:
  - `update_pre_backup_skipped`   (--no-backup or default-skip-with-config)
  - `update_pre_backup_requested` (--backup or config-enabled)
- `emitPreUpdateBackupPolicy` runs BEFORE any git mutation and resolves
  via the existing ResolveBackupPolicy helper from
  internal/cli/backup_policy.go, so a future writer slice can wire
  behind the same trigger without re-deriving precedence.
- Glyph mapping in cmd/gormes/update.go extended:
  - `*_skipped` → ℹ (dim)
  - `*_requested` → ◆ (bright cyan, mirrors Hermes' "◆ Creating
    pre-update backup..." marker)

Silent-default contract: when neither --backup nor --no-backup is set
and BackupConfigEnabled is false, NO backup-related evidence is emitted.
Most operators don't need to hear about the skipped backup on every
update run (parity with Hermes' default).

Tests:

- internal/cli/update_lifecycle_backup_test.go — 5 lifecycle tests:
  - NoBackupFlag emits skipped evidence with backup_disabled_by_flag reason
  - BackupFlag emits requested evidence with backup_forced reason +
    explicit "writer not yet implemented" detail (so operators don't
    expect a backup file to exist)
  - NoBackupBeatsBackup precedence rule
  - DefaultEmitsNoBackupEvidence (silent-default contract)
  - PreBackupRunsBeforeGitMutation (ordering is the contract for the
    follow-up writer slice — a backup taken after pull is useless for
    rollback)
- cmd/gormes/update_progress_ui_test.go — 2 UI tests:
  - BackupFlagFlowsToLifecycle proves cobra→options plumbing
  - PreBackupGlyphs proves ℹ for skipped, ◆ for requested

Implements row #2 from the gormes-update parity batch (audit agent's
recommended order). Reuses ResolveBackupPolicy and the structured
progress UX from c83d10e77. Unblocks a follow-up writer slice that can
wire the actual backup file creation behind the existing
update_pre_backup_requested trigger without re-deriving the precedence
rules.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(navivox): render tool-call artifacts in chat tiles

Extend the chat _ToolCallBody to list each NavivoxToolArtifact below the
tool-call summary with an attachment icon, kind label, title, and optional
artifact summary line. The artifact data was already populated by the
SshNavivoxChannel; this just surfaces it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(progress): add gateway whitelist parity row

Track Hermes' allowed_chats/channels/rooms whitelist parity for Telegram,
Slack, Discord, Mattermost, Matrix, and DingTalk as a P1 planned slice.
Sources cite Hermes 69d025e4a (Telegram/Mattermost/Matrix/DingTalk),
cd3ef685c (Slack), and PR #7044 (Discord). Generated docs and site
mirrors regenerated to match.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: sync install.sh mirror to source

Regenerate webpages/landing/public/install.sh to match root install.sh
which was updated to default to release binary fetch instead of source
build (commit 044cd264d). The mirror had drifted one commit behind.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(update): wire bundled-skill profile sync into update lifecycle

`gormes update` now runs the existing internal/skills.SyncBundledSkillsToProfiles
helper after a successful pull and renders Hermes-parity counts in the
structured progress UX. Skill sync is best-effort: errors emit
update_skill_sync_failed evidence but never fail the overall update.

What's added:

- internal/cli SkillSyncRunner type — abstract seam decoupled from
  internal/skills (the lifecycle package stays import-free; the cmd-side
  adapter does the conversion).
- internal/cli SkillSyncResult + SkillSyncProfileResult types — mirror
  the skills helper's per-profile counts (Added, Unchanged, Conflicts,
  Failed) without coupling.
- internal/cli emitSkillSync helper runs after pull but BEFORE gateway
  restart so the gateway can pick up newly-bundled skills on its
  restart cycle.
- formatSkillSyncSummary renders Hermes-parity:
    `default: +5 new, 12 unchanged, 1 user-modified (kept)`
  with zero-count buckets omitted to keep the transcript short.
- Two new evidence kinds: update_skill_sync_completed (per profile),
  update_skill_sync_failed (when seam returns an error).
- updateCommandSeams.SkillSyncFor — injectable factory; default builds
  the production adapter that calls SyncBundledSkillsToProfiles against
  `<checkout>/skills` and the active profile root from config.GormesHome.

Silent-default contract preserved: when the seam is nil (the case for
non-managed checkouts where bundled skills aren't applicable), no
update_skill_sync_* evidence is emitted. Same shape as the pre-update
backup silent-default from 56efc4042.

Tests:

- internal/cli/update_lifecycle_skill_sync_test.go (4 lifecycle tests):
  - NilSkillSync emits no evidence (silent default contract)
  - SkillSyncSuccess emits one completed evidence per profile with
    correct count strings ("+5 new", "user-modified")
  - SkillSyncFailure emits failed evidence with error message AND the
    update still returns success (best-effort contract)
  - SkillSyncRunsAfterPull verifies the ordering invariant
- cmd/gormes/update_progress_ui_test.go (3 cmd tests):
  - SkillSyncSeamWiredByDefault proves the default adapter wires when
    checkout/skills exists and GORMES_HOME is set
  - SkillSyncSeamNilWhenSkillsAbsent proves silent-default for
    non-managed checkouts
  - SkillSyncGlyphs proves ✓ for completed, ✗ for failed in the
    structured progress UX

Implements row #3 from the gormes-update parity batch (audit agent's
recommended order). Reuses the structured progress UX from c83d10e77
and the silent-default pattern from 56efc4042. Real adapter calls the
existing SyncBundledSkillsToProfiles helper from internal/skills (which
was already row-complete from prior planner work).

Owned divergence vs Hermes:
- Multi-profile fan-out: only the `default` profile is synced today.
  Named-profile discovery is a follow-up slice (parity row exists).
- "↑ updated" counts: helper does not distinguish updated-from-source
  vs unchanged. Counts surface as Added/Unchanged only.
- "− removed from manifest" counts: helper does not track removals
  from the bundled tree. Same follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(update): wire web UI rebuild step into update lifecycle

`gormes update` now rebuilds the web UI bundle after pull when a
`<checkout>/web/package.json` exists. Best-effort: failures emit
update_web_build_failed evidence but never fail the overall update
(matches Hermes' soft-failure contract — only `hermes web` treats a
build error as fatal).

What's added:

- internal/cli WebBuildRunner + WebBuildResult types (parallel to
  SkillSyncRunner pattern, decoupled from os/exec).
- 4 new evidence kinds:
  - update_web_build_completed   → ✓ green (success)
  - update_web_build_skipped     → ℹ dim (--skip-web or no package.json)
  - update_web_build_unavailable → ⚠ yellow (npm not on PATH)
  - update_web_build_failed      → ✗ bold (npm install / npm run build
                                   non-zero exit)
- internal/cli emitWebBuild helper runs after skill sync and maps the
  WebBuildResult to the right evidence kind based on Skipped/Unavailable
  flags or error.
- cmd/gormes --skip-web flag for operator opt-out.
- updateCommandSeams.WebBuildFor — injectable factory; default builds
  the production adapter that runs `npm install --silent` then
  `npm run build` in `<checkoutDir>/web`.

Silent-default contract preserved: when the factory returns nil (no
web/package.json in checkout), the lifecycle emits no web_build_*
evidence at all. Most non-managed checkouts and runtimes without a
web/ tree never see the feature.

Tests:

- internal/cli/update_lifecycle_web_build_test.go (5 lifecycle tests):
  - NilWebBuild emits no evidence (silent-default contract)
  - WebBuildCompleted emits completed evidence with seam Detail
  - WebBuildSkipped emits skipped evidence with seam Reason
  - WebBuildUnavailable emits unavailable evidence with seam Reason
  - WebBuildFailure emits failed evidence + update still returns success
- cmd/gormes/update_progress_ui_test.go (4 cmd tests):
  - WebBuildFactoryNilWhenNoPackageJson silent-default
  - WebBuildFactoryWiredWhenPackageJsonPresent factory wires runner
  - WebBuildSkipFlagShortCircuitsRunner --skip-web returns Skipped
    without invoking npm
  - WebBuildGlyphs verifies all 4 evidence kinds map to the right
    structured-progress glyphs

Implements row #5 from the gormes-update parity batch (audit agent's
recommended order — #4 goncho sync was deferred per the audit's "if
internal/goncho profile-sync surface does not exist yet, P2 is fine"
caveat; gormes/internal/goncho has no profile-sync surface today).

Reuses the structured progress UX from c83d10e77 and the
silent-default + best-effort patterns from 56efc4042 + f30f72c25.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: gateway allowed_chats/channels/rooms whitelist parity

Add shared WhitelistConfig + IsAllowed + ParseWhitelistConfig in
internal/gateway/whitelist.go with 8 focused TDD fixtures. Extend
TelegramCfg and SlackCfg with AllowedChats/AllowedChannels list config
fields plus accessor methods and env-var parsing. Wire whitelist
filtering into gateway Manager.allowed() check.

Ports Hermes v0.13.0 whitelist feature (69d025e4a + cd3ef685c) across
Telegram, Slack, and Discord platforms. Per-platform adapter wiring is
follow-up child rows.

* docs(progress): mark gateway whitelist row complete, regenerate progress surfaces

* feat(navivox): show selected agent indicator chip in chat AppBar

* chore: bump hermes-agent submodule

Advance hermes-agent from 7f369bfe5 to 7d66d30d7. Upstream picks up
TUI /sessions slash command, personality switch session preservation,
sessions+skills menu merge, personality fix merge, and kanban dashboard
tooltips.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(update): wire config schema migration prompt into update lifecycle

`gormes update` now checks the on-disk config schema version after the
web build step. When the version is outdated:

- With --yes: auto-applies via internal/config.MigrateConfigFile and
  emits ✓ update_config_migrate_completed.
- Without --yes: emits ⚠ update_config_migrate_needed advisory pointing
  the operator at `gormes config migrate` (or rerun with --yes).

When the config is already current, no evidence is emitted (silent
default — no nag on every update). Best-effort: errors from either the
check or migrate seam emit ✗ update_config_migrate_failed but never
fail the overall update.

What's added:

- internal/cli ConfigCheckRunner + ConfigMigrateRunner seam types
  (parallel to SkillSyncRunner / WebBuildRunner pattern; decoupled from
  internal/config so the lifecycle stays import-free).
- internal/cli ConfigVersionResult struct mirroring CheckReport.
- 3 new evidence kinds:
  - update_config_migrate_completed → ✓ (auto-applied)
  - update_config_migrate_needed    → ⚠ (operator action required)
  - update_config_migrate_failed    → ✗ (check or migrate error)
- internal/cli emitConfigMigrate helper with 7-branch decision table
  documented inline. Runs after web build, before gateway restart.
- cmd/gormes default adapters wrap internal/config.Check and
  internal/config.MigrateConfigFile into the seam shape.
- updateGlyphAndColor extended: `*_needed` suffix → ⚠ (yellow,
  operator-action-required category).

Tests:

- internal/cli/update_lifecycle_config_migrate_test.go (6 lifecycle tests):
  - NilConfigCheck silent default
  - ConfigAlreadyCurrent silent (no nag)
  - ConfigOutdatedWithoutYes emits needed + does NOT call migrate
  - ConfigOutdatedWithYes auto-applies + emits completed
  - ConfigMigrateError emits failed + update still succeeds
  - ConfigCheckError emits failed + does NOT attempt migrate
- cmd/gormes/update_progress_ui_test.go ConfigMigrateGlyphs verifies
  ✓/⚠/✗ for the 3 new kinds.

Implements row #6 from the gormes-update parity batch (audit agent's
recommended order). Reuses the structured progress UX from c83d10e77
and the silent-default + best-effort patterns established by 56efc4042
(backup), f30f72c25 (skill sync), and fa4a10d00 (web build).

Owned divergence vs Hermes' richer prompt:

- Interactive y/n at the prompt is intentionally not in this slice.
  Hermes' interactive flow would require a stdin reader seam and more
  branching. The slice ships --yes auto-apply + advisory-when-not, and
  the operator interactive path remains owned by the existing
  `gormes config migrate` command.
- Per-bucket missing_env / missing_config counts (Hermes' "⚠️ N
  required setting(s) need configuration") are not in this slice.
  CheckReport's Issues field carries them but the slice surfaces only
  version-skew. Detailed breakdown is a follow-up if operators ask.

Audit batch progress: 5 of 6 update-parity rows shipped (structured
UX, pre-update backup, skill sync, web build, config migrate).
Remaining: goncho sync (#4) — deferred until internal/goncho gains a
profile-sync surface.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(navivox): risk-aware approval banner badge

Replace the plain "Risk: $risk" text with a labeled badge: "High risk"
gets a warning icon, "Medium risk" / "Low risk" show a label, and unknown
or null risk renders no badge. The risk-aware badge makes high-risk
approvals scannable at a glance.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(strategy): ratify v1 differentiator and bump release-pipeline rows to P1

Add docs/content/building-gormes/strategy/v1-differentiator.md with the
ratified-on 2026-05-07 differentiator paragraph, 30-skill curated list,
and exclusion list. Promote release-pipeline / WhatsApp Baileys /
Signal-transport / Matrix-seam rows to P1/P2 priorities.

Repair a trailing-comma JSON syntax error introduced by the planning
note. Generated docs and site mirrors regenerated to match.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: bump version to 0.1.07

Sync cmd/gormes/version.go and webpages/landing/src/data/release.json.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: sync CHANGELOG.md with v0.1.07 release entries

* docs: add Hermes-style v0.1.07 release notes

The Operator-First Release. ~735 commits since v0.1.06.

Highlights:
- install.sh defaults to release-binary fetch (~7x faster fresh installs)
- Setup wizard prettified (framed headers, screen clear, color)
- gormes update parity: 5 of 6 audit-batch rows shipped (structured
  progress UX, pre-update backup, skill sync, web build, config migrate)
- Install isolation: iso-shellrc-leak + iso-systemd-hijack closed
- Repaired red Deploy gormes.ai workflow on main
- Loop \$/iteration cost metric

Same operating day as v0.1.06; date alias v2026.5.7 is shared.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: vendored WhatsApp Baileys bridge and removed QR pairing blocker

Vendored the Hermes-compatible Baileys bridge into scripts/whatsapp-bridge/ with provenance notice. Removed BLOCKER from preflight output. All WhatsApp tests pass.

* docs: parity sweep, agent lifecycle hooks row, WhatsApp QR closure

Bounded all-topic parity sweep across CLI/TUI, provider/auth, gateway/channels, tools, sessions/memory/Goncho, install/runtime, browser, docs, and release surfaces. Added agent lifecycle hooks (agent:start/step/end) row to Phase 5.O. Marked WhatsApp live Baileys QR pairing wizard complete. Bumped hermes-agent submodule to current upstream main (307c85e5c).

* feat(update): implement pre-update backup writer (zip of ~/.gormes)

Closes the writer-not-yet-implemented deferral from 56efc4042. When
--backup is set AND the writer seam is wired (the production default),
gormes update now creates a real zip archive of the operator's
~/.gormes before any git mutation.

What's added:

- internal/cli WriteBackupZip(ctx, sourceDir, destPath) (BackupResult, error)
  - Walks sourceDir with filepath.WalkDir
  - Skips paths matching IsExcludedFromBackup (checkpoints/, backups/,
    *.db-wal, *.db-shm, *.db-journal)
  - Streams to destPath.tmp first, renames on success (atomic)
  - Returns Path, SizeBytes (final archive byte count), DurationMs (wall)
- internal/cli BackupResult struct + BackupWriter seam type
- 2 new evidence kinds:
  - update_pre_backup_completed → ✓ green
  - update_pre_backup_failed    → ✗ bold
- emitPreUpdateBackupPolicy now invokes the writer when policy resolves
  to Requested AND BackupWriter seam is non-nil; fallback (nil seam)
  preserves the prior policy-only deferral evidence so existing tests
  don't regress.
- formatBackupSize / formatBackupDuration render human-readable size
  (B/KB/MB/GB) and duration (Nms or N.NNs) inline in evidence detail.
- backup_policy.go IsExcludedFromBackup now skips `backups/` subtree
  (so subsequent backups don't include all prior backups, growing
  geometrically).
- cmd/gormes defaultBackupWriterFor wires WriteBackupZip against
  config.GormesHome with destPath = `<home>/backups/pre-update-<UTC>.zip`.

Tests:

- internal/cli/backup_writer_test.go (3 unit tests):
  - IncludesPlainFilesExcludesSidecars verifies the walk picks up
    config.toml + auth.json + nested/dir/skill.md but skips
    checkpoints/snap.bin + log.db-{wal,shm,journal}
  - AtomicRenameOnSuccessNoTmpLeak verifies no .tmp leftovers
  - RejectsEmptyArgs guards misconfigured callers
- internal/cli/update_lifecycle_backup_writer_test.go (5 lifecycle tests):
  - BackupRequestedNilWriter keeps the deferral evidence (backward
    compat with the policy-only slice)
  - BackupWriterSuccess emits completed evidence with path + size + duration
  - BackupWriterFailure emits failed + update still succeeds (best-effort)
  - BackupWriterNotInvokedWhenNoBackup proves --no-backup short-circuits
  - BackupSizeFormatting covers B/KB/MB/GB scaling

End-to-end visual proof on a sandbox HOME:

    ⚕ Updating Gormes Agent...

    update branch: main
    ✓ update_pre_backup_completed    /tmp/.gormes/backups/pre-update-20260508T012115Z.zip (329B, 10ms)

Real backup file written: 329 bytes in 10ms. Future evolution: retention
pruning (keep last N), ConfigEnabled wiring from real config (currently
flag-driven only), per-bucket size summary in detail.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: agent lifecycle hooks (agent:start, step, end) with Kernel callback

Adds HookAgentStart/HookAgentStep/HookAgentEnd hook point constants and
AgentLifecycleHook callback to Kernel Config, firing at turn start, after
each tool iteration, and on all turn exit paths. Matches Hermes'
gateway/hooks.py lifecycle event dispatch pattern.

* docs: mark agent lifecycle hooks complete with evidence and sources

Updates progress.json row from planned to complete with validated contract,
Hermes source line references, implementation evidence, and test coverage.
Removes the row from agent-queue and next-slices. Regenerates mirror
progress.json copies for landing and go-renderer sites.

* chore: bump hermes-agent submodule for hooks.py lifecycle parity

Pulls upstream hermes-agent to faa13e49f for verified hook event dispatch
line references used in agent lifecycle hooks contract validation.

* flutter: Navivox key store providers and keys screen update

Adds Riverpod-based key store providers (identityStoreProvider,
serverStoreProvider) with stream-based watch pattern. Updates KeysScreen
to ConsumerWidget showing imported servers and identities, with empty
state for no keys.

* feat(update): pre-update backup retention pruning (keep newest 5)

After each successful pre-update backup write, the production adapter
now prunes older backup zips so the directory doesn't grow forever.
Default retention: 5 (matches Hermes' `updates.backup_keep` default).

What's added:

- internal/cli PruneBackups(backupDir, keep) (count, freed, err) helper:
  - Filters by `pre-update-*.zip` filename pattern (operator-owned files
    in the same directory survive untouched).
  - Sorts candidates by mtime, keeps newest `keep`, removes the rest.
  - keep <= 0 → no-op safety (operators who pass --backup-keep 0 by
    mistake should not lose data).
  - Missing directory → no-op (fresh installs with no prior backups).
  - Per-file remove errors are silently skipped — pruning is
    best-effort and must not block update completion.
- internal/cli BackupResult extended with PrunedCount + PrunedBytes.
- internal/cli emitPreUpdateBackupPolicy detail formatter appends
  `; pruned N older (M freed)` only when PrunedCount > 0; the no-prune
  common case (first --backup, recently-cleaned state) keeps the
  transcript short.
- cmd/gormes defaultBackupWriterFor calls PruneBackups after a
  successful WriteBackupZip with keep=defaultBackupKeep (5).

Tests:

- internal/cli/backup_prune_test.go (5 unit tests):
  - KeepsNewestN: 5 fixtures with monotonic mtimes, keep=2 → 3 removed,
    newest 2 survive.
  - NoOpWhenAtOrUnderKeep: 1 file, keep=5 → no-op.
  - IgnoresNonBackupFiles: operator's NOTES.md + manifest.json +
    other-archive.tar.gz survive a prune that reduces 3 backup files to 1.
  - KeepZeroIsNoOpSafety: keep=0 and keep=-1 both no-op.
  - MissingDirReturnsNoOp: non-existent dir returns (0, 0, nil).
- internal/cli/update_lifecycle_backup_writer_test.go (2 lifecycle tests):
  - BackupCompletedDetailIncludesPruneInfoWhenPositive: PrunedCount=3
    surfaces "pruned 3 older" + "freed" in evidence detail.
  - BackupCompletedDetailOmitsPruneSuffixWhenZero: PrunedCount=0 emits
    no prune phrasing.

End-to-end verification with a sandbox HOME seeded with 6 backup files:

    ⚕ Updating Gormes Agent...

    update branch: main
    ✓ update_pre_backup_completed    /tmp/.gormes/backups/pre-update-20260508T014058Z.zip (329B, 2ms); pruned 2 older (240B freed)

After run: 5 files in backups/ (newest 5 of the 6 seeds + 1 new write,
2 oldest removed) — matches the keep=5 contract exactly.

Future-natural follow-up: wire `updates.backup_keep` from real config
into a configurable retention budget (currently hardcoded to 5).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: plugin lifecycle hook transform_llm_output with TDD fixtures

* docs: mark transform_llm_output complete, unblock 7 parity rows, regenerate surfaces

* feat: gateway allowed_chats whitelist config and update test coverage

* fix(update): survive missing config.toml on fresh install

* feat(signal): add signal-cli HTTP JSON-RPC + SSE bridge bootstrap layer

Implement the fakeable Signal transport/bootstrap layer that bridges
the existing signal.Bot seam to the signal-cli HTTP daemon. Five TDD
acceptance tests cover config/health/lock lifecycle, SSE URL encoding,
attachment fetch params, reconnect/recovery, and outbound send with
typing-stop through a fake HTTP transport.

Progress: Phase 7.A Signal subphase is now fully complete (4/4 rows).

* fix(progress): update Phase 7.A test expectations for completed Signal bootstrap row

* docs: close 6 inert umbrellas (5.O + 5.V), mark Matrix seam complete, regenerate surfaces

* fix: add hermes gateway list to CLI parity manifest and tests

* feat(matrix): add shared-chassis bot seam over threadtext contract

* chore: pre-existing update lifecycle, backup writer, config writer changes

* feat: add restore command, backup restore lifecycle, and update tests

* chore: pre-existing status command changes

* feat(restore): dry-run validates zip before printing would-extract

Restore dry-run (without --yes) now validates the zip archive is openable and free of path-traversal entries BEFORE showing the "would extract" message. A corrupt or malicious zip will now surface the rejection immediately instead of misleading the operator.

* feat(auth): add --json flag to auth status for machine-readable output

gormes auth status <provider> --json now emits a parseable JSON document with the same redacted credential fields the human surface renders. Enables credential-health monitoring in CI/cron without scraping the human row.

* fix(restore): add zip pre-validation to RestoreFromZip extract path

Extends restore zip validation to the actual extract path (not just dry-run). Pre-validates the entire archive before extracting any files, so a malicious entry late in the zip cannot land earlier safe entries on disk.

* refactor(doctor): route output through cobra writers, use newExitCodeError instead of os.Exit

* feat(doctor): add JSON output mode and constructor command pattern

- internal/doctor: add StatusSkip, MarshalJSON, and JSON struct tags
- cmd/gormes/doctor: refactor doctorCmd to newDoctorCommand() constructor
  with doctorReporter for dual text/JSON output, eliminating shared var
  test contamination
- cmd/gormes: wire newDoctorCommand() in root, update tests to use
  newRootCommand() with subcommand args
- cmd/gormes/doctor_runE_test: add fleet-health JSON assertion test

* test(doctor): add failed-field assertion for JSON monitoring contract

* feat(doctor): track failed bool in JSON output for monitoring parity

* feat(doctor): add drSeverity and pending incremental JSON refinements

* feat(version): add git_commit field to --json output for fleet binary verification

* feat: add git_commit field to --json version output and refactor versionCmd to constructor pattern

- Refactor versionCmd from package-level var to newVersionCommand() constructor
  to prevent cross-test flag contamination (same anti-pattern that broke
  doctorCmd before its constructor refactor)
- Add TestVersionCommand_ConstructorReturnsIndependentInstances test
- Add git_commit field to versionReportJSON, populated via ldflag in CI/Makefile
- Update deploy-gormes-www.yml, release.yml, Makefile build commands with
  -X main.GitCommit=${GIT_COMM…
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant