All notable changes to GIMO will be documented in this file.
The format is based on Keep a Changelog,
and this project adheres to Semantic Versioning.
Two changelogs coexist intentionally (clarified 2026-05-07):
- This file (root
CHANGELOG.md) — terse Keep-a-Changelog format inEnglish. Audience: external integrators, release notes, semver bumps.
Source of truth for shipped versions.
docs/CHANGELOG.md— long-form narrative inSpanish covering each rollout in detail (motivation, audit findings
addressed, files touched, sub-bugs discovered). Audience: contributors
tracing repo history. Sliced by date and feature cluster.
Bullet entries here should mirror the headline of the corresponding
narrative block in
docs/CHANGELOG.md; both files should be updatedtogether for any release-grade change.
Validación del eje "modelo fuerte" del context-engine (qwen3-coder:480b-cloud,
Ollama, EUR0): TOON vs JSON −4pp (ruido), L3 patch vs full +0pp. Es decir, los
modelos fuertes leen TOON/patches sin penalización, mientras los pequeños
degradan (−11/−60pp). → política gateada por capacidad, con la señal
canónica que GIMO ya tiene: ModelInventoryService.find_model(model).quality_tier
(determinista, sin tokens LLM, refinada por benchmarks + GICS · la misma que usa
ModelRouterService). No se inventa heurística paralela.
Tier bajo (≤2): TOON solo bajo presión, sin L3. Tier alto (≥4): TOON universal
(−37% tokens) + L3 en refetches (−68%). Documentado en
context_engine/README.md + eval/EVAL_RESULTS.md. Implementación de las piezas
de tier alto: diseñada + validada, pendiente de build. Evals ahora
parametrizables vía GIMO_EVAL_MODELS.
Eval de task-success de L3 (context_engine/eval/l3_patch_eval.py) en los
modelos pequeños locales: mandar un JSON Patch del refetch en vez del resultado
completo ahorra 68% de tokens pero **degrada la precisión −19/−28pp global y
hasta −60pp en las filas que cambian** (el dato que el refetch quiere conocer).
Falla el gate justo donde habría presión de budget para aplicarlo. No se
implementa — diferido como L1. La disciplina eval-first evitó construir
refetch-detection + emisión de patches + cambio de protocolo por fe.
Se rescató la rama local context-engine (stack de 8 capas de compresión de
contexto, nunca mergeada, 111 commits atrás) y se consolidó lo real y validado
en tools/gimo_server/context_engine/ (README decision-record, benchmark
reproducible, eval de task-success). No se portó el scaffolding especulativo de
L4-L8 (distill, don't design).
Validación de L2 (TOON) en los modelos pequeños locales de la mesh (Ollama,
EUR0): TOON degrada vs JSON completo (−11/−12pp en 2 de 3 modelos) → no aplicar
universal; pero gana al truncado lossy —la alternativa real cuando el contexto
no cabe— por +29 a +50pp. Regla validada y ya implementada: TOON solo bajo
presión de budget, nunca en cada call. Estado por capa: L0 hecho, L1 rechazado
(negativo), L2 hecho+validado, L3-L8 diseñados.
El adapter Anthropic ya cacheaba system + tools (cache_control: ephemeral),
pero el historial de mensajes —que crece cada turno— se re-mandaba sin cachear:
el mayor desperdicio en sesiones largas. Nuevo
_apply_conversation_cache_breakpoint pone un tercer breakpoint en el último
bloque del último mensaje, convirtiendo todo el prefijo en cache read (~10% del
coste) en los turnos siguientes. Usa 3 de los 4 breakpoints de Anthropic; se
salta conversaciones cortas (< PROMPT_CACHE_MIN_CHARS) donde el write
surcharge no amortiza. Lossless. Cierra el gap de L0, la mayor palanca de
ahorro según el benchmark del context-engine (caching domina el stack).
Port en Python del serializer TOON (Token-Oriented Object Notation) que Janus
ya usa para el árbol de accesibilidad: tools/gimo_server/utils/toon.py. Un
array JSON de objetos uniformes (search hits, listados, filas) se re-serializa
a name[N]{cols}: + filas tipo CSV — recorta ~30-60% de tokens sin pérdida de
información para el modelo (~44% medido en un search_text de 50 hits:
1253→711 tok). El agentic loop lo aplica como Pass 0 lossless en
_trim_messages_to_budget, antes de cualquier truncado lossy. Esto reduce
coste/tokens (no añade un LLM de resumen como Webwright). Solo convierte el
caso de alta ganancia y sin ambigüedad; el resto se deja intacto.
El agentic loop, al exceder el presupuesto de contexto en modelos constrained
(≤8192 tokens, p. ej. modelos locales de la mesh), descartaba los turnos
viejos a un hueco ciego — perdiendo qué ficheros se tocaron, qué selectores
/comandos funcionaron y qué errores ya se resolvieron. Ahora
_apply_sliding_window pliega un digest determinista del span descartado
(tools usadas, targets, errores vistos) dentro del system prompt. Sin llamada
LLM extra (cero coste/latencia), acotado por _DIGEST_RESERVE_TOKENS para que
re-añadirlo no rompa el presupuesto. Inspirado en la compactación de
microsoft/Webwright; diseñado como seam para un summarizer LLM futuro.
Nuevo subdominio docs.giltech.dev: un único sitio de documentación para
todas las aplicaciones de Gred In Labs Technologies (GIMO, GICS, GISH, bio),
patrón SOTA multi-producto (dominio centralizado + sección por producto, como
HashiCorp developer.hashicorp.com o Stripe docs.stripe.com).
apps/cf-workers/docs (giltech-docs) enlazado al subdominio vía custom_domain = true. Sirve la landing con el grid de
productos y renderiza el CHANGELOG.md canónico en /changelog
(import de texto · single-source: editar el changelog y re-desplegar lo
refleja).
el enlace de /account/support ahora resuelve a docs.giltech.dev/changelog.
(Mintlify/Fumadocs) se monta encima cuando se elija.
Cierre del backlog R29 Phase 3 que quedaba sin commitear desde el
2026-05-22 (C1 ya había aterrizado en 0a678b65). Cada fix tiene
análisis canónico previo en docs/audits/E2E_ROOT_CAUSE_ANALYSIS_20260522_R29.md.
Política release: 3 pasadas idénticas con 0 errores antes de commit.
R29C2 · POSIX path normalization en project_root()
gimo_cli/config.py::project_root añade helper _looks_like_posix_in_windows + check Path.exists() post-resolve. Cierra el bug donde
git rev-parse --show-toplevel desde PowerShell con un git MSYS/WSL
emite /home/shilo/... y Path().resolve() lo convierte en
C:\home\shilo\... (path inexistente · todo .exists() posterior
miente con False engañoso).
/home/, /c/, /mnt/, /cygdrive/, /root/, /tmp/. No flagea URI-style con drive letter (/C:/...) ni paths Windows
canónicos.
tests/unit/test_project_root_posix_normalization.py.R29C3 · Provider healthy predicate · not_required state
provider_diagnostics_service.py predicate ahora acepta auth_status in {"ok", "not_required"}. _auth_probe devuelve
"not_required" para providers locales con 4 heurísticas:
(1) requires_auth=False explícito en ProviderEntry,
(2) provider_type == "ollama",
(3) provider_id empieza por ollama/local-/lan-,
(4) base_url contiene localhost/127./0.0.0.0.
models/provider.py::ProviderDiagnosticEntry.auth_status Literal extendido para incluir "not_required".
CLI mientras OperatorStatusService lo marcaba como active_provider.
tests/services/providers/test_diagnostics_healthy_predicate.py.R29C4 · Token mismatch log downgrade WARNING → DEBUG
tools/gimo_server/config.py::_load_or_create_token baja a DEBUGel log de "file ≠ env divergence" cuando file precedence resuelve
sin error. Post-R28 Change 1 la doctrina canónica es "file wins ·
env override opcional CI/container", por lo que la divergencia ya
no es anomalía operacional sino operación normal.
~/.gimo/server.log en cada boot lab.tests/unit/test_config.py que sella el invariante(level=WARNING NO captura el record, level=DEBUG SÍ).
R29C5 · Paginación /ops/threads
ConversationService.list_threads añade limit (default 50, cap 1-10000), offset (default 0), since_updated_ms (default None,
cursor temporal). Devuelve top-N por updated_at descending.
routers/ops/conversation_router.py::list_threads expone los3 params como query string con validación FastAPI.
gimo_cli/commands/threads.py::threads_list delega paginación al servidor en vez de trim client-side. Añade flag --offset para
consumo manual del API.
MCP tool result quota (1 MB) y hacía que gimo_threads_get no
retornase nada útil.
tests/unit/test_list_threads_pagination.py.Verificación V1 · Janus/Aleph/Workspace snapshot
Pasada de verificación sobre los 3 subsistemas que MEMORY listaba
como "implementados" pero no se habían auditado esta sesión.
Confirmados con tests existentes: Janus (34/34), Aleph (36/36),
Workspace snapshot endpoint (8/8). No son stale memos · estado real
estable.
Helper · script email account-banned interactivo
scripts/send-account-banned-email.mjs nuevo. Envía la plantilla public-account-banned por SMTP2GO sin disparar el flow de ban
(no toca Firestore, no escribe banned_hash, no revokeRefreshTokens).
+ confirmación con resumen antes de mandar.
SMTP2GO_API_KEY + flags --to/--name/--reason.
email original, QA puntual en producción. NO añade endpoint al
admin compose (deliberado: la plantilla terminal queda fuera del
surface admin público para evitar uso accidental).
Verificación gates · 3 pasadas idénticas con 0 errores:
Sesión de cierre del backlog "bloqueos pricing v2" documentado en
memory/project_pricing_v2.md. Diagnóstico: 6 de 7 bloqueos eran
stale memos del 2026-05-22 que el código ya tenía resueltos
en sprints posteriores sin sincronizar nota. 1 era drift real entre
TS y Python (capability_registry_export faltaba en tier-defs). 1
es feature aún no implementada (GICS manual editing) y queda
deferred sin bloquear pricing.
ADR-0012 · Controller canonical semantics:
docs/adr/0012-controller-canonical-semantics.md documenta losdos límites separados (cap tier vs invariante runtime "1 core
activo a la vez") y la matriz de mapeo entre los 3 vocabularios
(HardwareClass × DeviceRole → DeviceMode) que existían sin bridge
declarado.
apps/web/src/lib/mesh/device-mapping.ts (TS) + mirror tools/gimo_server/services/mesh/device_mapping.py (Python) con
funciones suggestedDeviceMode, controllerSuitability,
isCoreDeviceMode y DEVICE_MAPPING_MATRIX data-driven.
apps/web/src/components/onboarding/steps/StepChooseDevice.tsx:copy actualizado para distinguir "primer controller pinned en
Free" vs "controllers múltiples con traspaso en Operator+".
Badge visual de controllerSuitability por device sin bloquear.
apps/web/src/app/account/devices/page.tsx: sección CoreTransferSection con instrucciones reales de traspaso
(gimo stop A → gimo start B). Solo visible para tiers con
controllerTransferable: true y ≥2 controllers registrados.
/api/account/devices ahora expone tier, maxRegisteredControllers, controllerTransferable para que
la UI gate las secciones premium correctamente.
Drift fixes:
apps/web/src/lib/licensing/tier-defs.ts añade capability_registry_export al enum LicenseFeature con doc
explicando qué exporta (capability profile per-model ×
per-task_type acumulado por CapabilityProfileService backed by
GICS keys). Activado en TIER_MESH, TIER_TEAM, TIER_ENTERPRISE.
Backend ya gateaí server-side con HTTP 402 desde sprint anterior;
ahora frontend lo refleja.
tests/unit/test_mesh_single_core_invariant.py rescatado: 4 tests pre-existentes fallaban en HEAD porque conftest.py pinea
tier a free (cap=1) y el cap-check evalúa antes que el
invariante. Añadido monkeypatch del fixture para forzar tier
ilimitado (team) cuando el objetivo es probar el invariante
runtime aislado del cap. El cap tiene cobertura propia en
test_registered_controllers.py.
Tests añadidos (38 nuevos):
apps/web/src/lib/mesh/__tests__/device-mapping.test.ts · 23tests TS (cardinalidad, matriz canónica, parametric por fila ADR,
cobertura isCoreDeviceMode invariant).
tests/services/mesh/test_device_mapping.py · 15 tests Pythonparalelos · mirror exacto del set TS.
Bloqueos pricing v2 cerrados como stale memos (sin código):
Mesh, no 3 tiers comerciales. DeviceMode enum + doc canónico
ya lo cubren.
docs/archive/SUB_DELEGATION_PROTOCOL.md, cap
max_concurrent_child_runs purgado de tier-defs, no
comercializado. Código interno (child_run_service,
spawn_agents) sigue activo solo para casos del orchestrator.
max_registered_controllers y funcional desde sprint 2026-05-23
(journal:567). Tests + enforcement existen.
usa behavior_confidence_full, backend conserva /ops/trust/*
URLs para no romper MCP/CLI. Documentado en
behavior_confidence.py:3-9.
Workspace pairing es mecanismo técnico intra-cuenta (pairing
code para device del MISMO user). Multi-cuenta = Team tier
features (sso, license_pooling, shared_skills_repo) ya
vendido. Cosas distintas.
(solo GET read-only en gics_patterns_router.py). Definir tier
cuando se construya · no bloquea pricing v2 actual.
Verificación gates · 3 pasadas idénticas con 0 errores:
device_mapping +4 invariant rescatados)
Pasada con Semgrep (TS+Next+OWASP+JS+Node rulesets) + npm audit + secrets
sweep manual. Suite sigue 350/350 green, tsc clean.
16.2.4 → 16.2.6 por GHSA-8h8q-6873-q5fj (Server Components DoS). Non-breaking, range vulnerable >=16.0.0 <16.2.5.
npm audit fix + overrides en apps/web/package.json →0 vulnerabilities (12 → 10 → 8 → 0). Overrides aplicados:
postcss@^8.5.10 (cierra GHSA-7fh5-64p2-3v2j XSS via </style>),
uuid@^11.1.1 (buffer bounds), brace-expansion@^5.0.6 (numeric DoS),
protobufjs@^7.6.0 (recursive JSON DoS). Tests 350/350 verdes tras
overrides · no regression upstream. Next.js 16.2.6 sigue pinneando
postcss@8.4.31 internamente, el override force-dedup a 8.5.15.
scripts/smoke_synth_dispute.py (era el stripe listen local, no
production, pero commited en repo). Movido a env
STRIPE_WEBHOOK_SECRET / STRIPE_LISTEN_SECRET con fail-closed.
en auth/callback/page.tsx:135 (open-redirect / tainted-redirect)
son false positives porque el sanitizer resolveSafeReturn
(líneas 46-60) valida same-origin + path-relativo + fallback default,
pero Semgrep no traza taint cross-function. Marcados con nosemgrep
comments justificados. Auditoría manual de los otros 30+ redirect
call-sites del repo confirma sanitización correcta (regex param
validation, same-origin clones, env-configured bases).
error" + 46 timeouts + 18 syntax errors sobre 1k+ archivos. Probable
bug Semgrep 1.161.0 en Windows con paths largos. La cobertura real
fue ~30% del repo. Estado: aceptable como baseline · próxima pasada
conviene Linux runner o CodeQL para cobertura completa.
.github/workflows/ci.yml extendido para que cada push a main y cada
PR ejecute las verificaciones que la ronda 8 hizo a mano:
python-gates: añadidos p/nextjs, p/owasp-top-ten, p/javascript, p/nodejs a los rulesets que ya corrían (p/python,
p/typescript, custom). Severity ERROR gate-blocking; WARNING como
artifact informativo. Linux runner evita el bug de encoding cp1252
que vimos en Windows · cobertura completa del repo.
npm audit en job web (apps/web): - GATE bloqueante con --audit-level=high · falla CI si hay
vulnerabilidades critical o high.
- Step informativo separado lista moderate/low con título legible y
sube el JSON como artifact (npm-audit-apps-web).
- Override path documentado: para risk-accept temporal de un HIGH
sin fix patch-level, bajar a --audit-level=critical Y dejar nota
en CHANGELOG (CVE id + razón no-explotable + bloqueo + deadline).
overrides block en apps/web/package.json: cierra postcss /uuid / brace-expansion / protobufjs en chain firebase-admin sin
esperar bump major upstream.
Este bloque cierra el feedback loop: cualquier CVE futuro o secret
nuevo aterrizado en el repo dispara CI antes de merge en main · no más
"esperar a la próxima ronda manual".
Auditoría adicional con 4 agentes ortogonales (regression, test
coverage, doc drift, edge cases concurrency/legal). 9 hallazgos
resueltos, 0 pospuestos. Suite pasa 350/350 (+27 tests nuevos).
/api/license/regenerate ahora rechaza 410/409 si el user tiene deletionCompletedAt/pendingDeletionAt. Sin esto un
user mid-deletion podía extraer una key nueva durante la ventana.
export const dynamic = "force-dynamic" en `/api/account/ profile y /api/account/undo-deletion` para evitar caché stale-auth
bajo ISR.
initDunningState + cron dunning-tick + cron drip-tick skipean usuarios con pendingDeletionAt/
deletionCompletedAt. Antes el user que pedía cerrar podía recibir
cadena dunning D1/D4 o drip day3/day7 durante los 30d.
revertStripeCancellation ya no es silent success: siencuentra subs canceladas SIN marker `cancelled_by="account_deletion_
request" levanta stripe_warn_unreverted` y el redirect del undo va
a ?status=ok_check_portal con CTA al Portal de facturación.
con tabla, aclarando que el cron emite ≥17 queries (audit×2,
licenses×2, webauthn_credentials sub+parent) sobre 16 colecciones.
safeEqualString (6), pickFingerprintComponents (5), ban-hash (8), upsertLicenseDoc
preserve-consent (4), auth/session con 3 errores nuevos de
upsertUserOnLogin. Los tests **descubrieron y fixearon un bug
real**: safeEqualString usaba .length (UTF-16 code units) en vez
de Buffer.byteLength, lo que hacía que comparaciones con UTF-8
multi-byte ("café" vs "cafe") lanzaran RangeError en lugar de
devolver false limpio.
firestore.rules añade reglas explícitas defense-in-depth para processed_events, audit, dunning_state,
refresh_tokens, revoked_tokens (todas server-only).
PUT /api/account/profile rechaza 409/410 si el userestá en ventana de cierre · evita drift entre confirmación al user
y purga posterior del tombstone.
BATCH_LIMIT=25 process vs `limit(50)fetch` para que el operador entienda el margen 2× anti-tombstone.
Continuación del bloque legal (commits d8b389a9 / 526f3d70 /
ed62f02c) cerrando 50+ hallazgos de 6 rondas de auditoría adversarial
(initial 4-agent + 5 rondas críticas). Estado final: 323/323 tests
green, tsc clean, zero amenazas explotables.
Added · GDPR Art. 17 self-service closure
DELETE /api/account (ventana 30d · undo HMAC · cancel Stripe).GET/POST /api/account/undo-deletion (link email sin auth + UI concookie).
GET /api/account/export (Art. 20 portabilidad · 1/h rate-limit).GET/PUT /api/account/profile (Art. 16 rectificación · displayName).POST /api/cron/process-deletions (Cloud Scheduler cada 6h purga 16 colecciones · ver docs/runbooks/account-deletion-ops.md y
ADR-0010).
/account/close-account, /undo-result (esta FUERA de /account/layout.tsx para no caer bajo auth gate).
public-account-deletion-{requested,completed}.Added · Consent persistido v2 (LSSI-CE + Art. 7 RGPD)
LicenseConsent opcional en LicenseDocV2(terms_version, privacy_version, immediate_start, accepted_at).
upsertLicenseDoc preservando consent previo (hasConsentMetadata gate evita borrar con objeto vacío).
requireLegalAcceptance: true opt-in en upsertUserOnLogin para signups Free (users/{uid}.termsAcceptedVersion etc.).
/api/billing/checkout + /api/auth/session.Added · Pricing v2 yearly + Team seat-addon
STRIPE_PRICE_*_YEARLY).STRIPE_PRICE_TEAM_SEAT_ADDON{_YEARLY}) comosegundo line item Stripe (cap 47 extra = 50 total).
ConfirmForm con toggle Monthly/Yearly + input seats inline (serverdetecta env vars y oculta toggles si no configurados).
Added · Audit canónico + 6 admin destructivos
lib/audit/ttl.ts exporta CANONICAL_AUDIT_EVENTS (20 events: 14user-triggered + 6 admin destructivos, ambos buckets warn/critical).
emitWebAudit (issue-bond, refresh-bond,reactivate, cold-room/issue, admin/bonds/revoke).
admin_license_banned, admin_license_revoked, admin_license_created,
admin_user_approved, admin_email_broadcast,
admin_test_email_sent) — Art. 30 RGPD + forensics SOC2.
Added · Helpers
lib/auth-middleware.ts:getServerSessionUser (server-component-safe con checkRevoked=true siempre).
lib/crypto/timing-safe.ts:safeEqualString (constant-time, aplicadoa 3 crons + orchestrator/verify + webhooks).
lib/auth/fingerprint.ts:pickFingerprintComponents (filter a 5 keyscanónicas antes de persistir).
lib/licensing/ban-hash.ts (dos índices: full + email-only).Added · Docs
docs/adr/0010-gdpr-self-service-account-closure.md.docs/adr/0011-consent-persisted-license-v2.md.docs/runbooks/account-deletion-ops.md.Changed · Security hardening (STRIDE)
requireAdmin con checkRevoked: true.admin/license/ban llama revokeRefreshTokens.verifyAuth Bearer path con checkRevoked: true (antes 1h ventana).license/validate migrado a checkRateLimit Upstash + length caps.orchestrator/verify con safeEqualString (timing-safe).license/deactivate ownership cubre v1 (userId), v2 (uid), admin.clientIpFrom respeta TRUSTED_PROXY_HOPS env (defense-in-depthXFF spoofing sin proxy de confianza).
issue/refresh/reactivate bond, cold-room/issue, billing/portal) con rate-limit + length caps.
orchestrator/verify no devuelve detail: message crudo (logs loconservan).
auth/session, cli/issue-bond).Changed · UI + flows
AccountStatusBanner añade caso pending_payment.PasskeysCard añade sección "recovery info" + link /login.account/plan 3 botones cableados (Upgrade, Manage Stripe Portal,Regenerate con panel rawKey one-shot + copy clipboard).
/login resuelve return paths contra APP_URL (no GIMO_DOMAINS.root).
/auth/callback redirige a /empezar-gratis?reason=consent_requiredcuando session devuelve 400.
/account/support lee ?topic=dsar|ban_appeal|billing|general, mailto a privacy@/disputes@/billing@/support@.
noindex si faltan env vars LEGAL_RESPONSIBLE_*`.
Changed · Schema + indexing
firestore.rules licenses/{licId} cubre v1 (userId), v2 (uid / licId == auth.uid); subscriptions cubre v1+v2.
stripe-price-map.ts añade STRIPE_PRICE_TEAM_BASE_YEARLY y _TEAM_SEAT_ADDON_YEARLY.
upsertLicenseDoc preserva consent del doc previo si caller no lotrae (anti-overwrite).
Changed · Docs operacionales
apps/web/docs/STRIPE_SETUP.md reescrito con pricing v2 canónico (env names OPERATOR/MESH/TEAM_BASE/TEAM_SEAT_ADDON + yearly).
apps/web/docs/LICENSE_SCHEMA.md reescrito con schema actual(consent, pause_history, enterprise tier, internal_dev no-lifetime).
docs/PRIVACY_TREATMENT_REGISTER §2 documenta flow real cierre.docs/PRIVACY_TREATMENT_REGISTER §5 rename Pro→Mesh./privacidad §8 añade sección "Rutas self-service"./privacidad §3.6 enumera 14 eventos auth canónicos (vs drift 10/13/9previo).
apps/web/README.md tabla endpoints completa (account/cron/webhooks/webauthn/admin).
Fixed · 50+ hallazgos de 6 rondas auditoría
*Críticos*:
process-deletions starvation (tombstone limpia pendingDeletionAt).process-deletions campo audit actor (post-B5.3) + uid legacy.process-deletions borra email_events por recipient (no uid). (gate hasConsentMetadata).
stripe-price-map faltaba mapping TEAM_BASE_YEARLY y TEAM_SEAT_ADDON_YEARLY → tier null → v2 no escrito.
/account/undo-result capturada por AccountShell auth gate → movida a /undo-result raíz fuera de /account/layout.tsx.
admin/license POST creaba internal_dev con isLifetime: true → features Mesh sin janus_dev_internal (staff sin Janus). Ahora
isLifetime: false + lifetime UI prioriza v2.is_lifetime.
/api/account reescribía deletionCompletedAt: null sobretombstone si user reintentaba → 410 con guard explícito.
regenerate license rawKey no llegaba a UI (router.refresh() nore-fetcha cliente) → panel destacado one-shot con copy + warning.
/login resolvía return path contra giltech.dev → ahora gimo.giltech.dev.
?undo= invisible para no-autenticados → página estática /undo-result fuera de auth gate.
*Serios*:
undo-deletion no revertía cancel_at_period_end Stripe.admin/license/revoke users.doc(licenseId).get() solo funcionaba v2 → ownerUid resolver cubre v1+v2.
Removed
components/gimo-landing.tsx + test + lib/default-content.ts(huérfanos solo consumidos por su propio test).
TODO C4 Founding Lifetime path en webhook stripe (tier retirado2026-05-21).
Manual operator follow-ups (post-deploy):
1. Cloud Scheduler process-deletions-cron cada 6h con
X-CloudScheduler-Token (ver runbook).
2. Stripe Dashboard crear price IDs yearly: OPERATOR_YEARLY,
MESH_YEARLY, TEAM_BASE_YEARLY, TEAM_SEAT_ADDON,
TEAM_SEAT_ADDON_YEARLY → env vars correspondientes en Cloud Build.
3. Env vars LEGAL_RESPONSIBLE_NAME/NIF/ADDRESS para desbloquear las
3 páginas legales (anti-leak banner).
4. Índices compuestos Firestore opcionales para export ordenado:
audit (uid asc, timestamp desc), `sent_emails (uid asc, sentAt
desc), email_events (uid asc, receivedAt desc)`.
5. Cloudflare Email Routing 7 alias (`support/billing/privacy/disputes/
legal/security/hello@giltech.dev`).
Cleanup pass surfacing three artifacts that drifted out of sync during
the prior B-blocks and T1-CRIT/MAJ sprints. No behavioral change in
production; deuda invisible removed.
apps/web/src/lib/links/gimo-domains.ts exports GIMO_DOMAINS (root / auth / orch / docs / status /
downloads) resolvable at build time via NEXT_PUBLIC_GIMO_*_URL env
vars. 33 hardcoded https://*.giltech.dev literals across 22 source
files migrated to constant refs (account components, login, emails,
webauthn recovery, unsubscribe builder, admin test-transactional,
webauthn config). Account home also collapses the duplicate APK
download href to reuse APK_MESH_URL from the downloads manifest. One
edit to rotate the domain (or override per env for staging /
preview).
GIMO_INTERNAL_KEY drop — removed from --set-secrets in apps/web/cloudbuild.yaml. Runtime call sites already prefer the
bundle via getHmacKey("internal_key"); bundle has been stable since
F1 sprint. Manual destroy of the GCP secret gimo-landing-internal-key
is pending (operator action).
migration-dryrun.test.ts was modeling the pre-pagination .get()`
shape of migrate-licenses.ts; the script was refactored in commit
f742ac4c (perf,refactor) to paginate via `orderBy(documentId()).
limit().startAfter().get() + batch read via adminDb.getAll(...refs)`
+ subscriptions batched via where("licenseId", "in", chunk), leaving
3 tests red. Fixture rewritten to model the new chain (chainable mock,
paginated slices keyed by docId cursor, batch refs stamped with
_uid for getAll, in operator on subscription mock). Global
@/lib/firebase-admin mock in apps/web/src/__tests__/setup.ts
extended with startAfter chain, doc().create(), and
adminDb.getAll() for any future test that hits the paginated path.
Verification:
apps/web: tsc clean, 258/258 vitest green.tools/orchestrator_ui: tsc clean, 205/205 vitest green.Commit e3c3a292.
Multi-block sprint post-research masiva (10 agentes producto/UX/sec/cost).
/onboarding server component con 6 steps client (StepWhatIsGimo, StepYourMesh,
StepCoreLocation, StepPlanFlexibility, StepChooseDevice,
StepDownload); explica que en Free el Core se pinned al primer device
y solo paid permite transferir. Backend enforcement
max_registered_controllers por tier en
tools/gimo_server/services/licensing/registered_controllers.py +
integración en mesh/enrollment.py::ensure_core_slot_available.
cuando un agente encuentra una piedra trivial puede pausar el run, crear
un context_request, emitir SSE y esperar la directiva del usuario en
lugar de abortar y forzar relanzamiento (que quema tokens). Wrapper
AgenticLoopService.pause_and_ask_user, extensión
OpsResumeRunRequest.context_directive, nuevo campo
OpsRun.pending_context_request_id, UI agent-question-card.tsx en
orchestrator_ui, checkpoint hook on pause/resume, tests E2E
ask→pause→resume.
- Trust Dashboard → Behavior Confidence rename + gating real en
PUT /trust/circuit-breaker/{dim} (mesh+) y GET /trust/ids/insights
(operator+) via nuevo helper behavior_confidence.py (HTTP 402
estructurado con CTA payload).
- Shared Skills Repo backend (Firestore
organizations/{orgId}/shared_skills/{skillId}) — 3 endpoints
GET/POST/POST install bajo /api/shared-skills/* con gate tier
team+/enterprise+/internal_dev+. 4 tests.
- Capability Registry export — GET /ops/capabilities/export que
devuelve CapabilityProfile completo, gating mesh+. Helper
capability_registry.py + método CapabilityProfileService.export_all().
- Aleph audit en skills — AlephService.record_skill_event + wire
en install/publish/execute con metadata por acción.
tailscale_auth_key en MeshDeviceInfo + doc
docs/guides/byo-tailscale.md + ADR-0008 (mesh access sin infra
GIMO).
enterprise formal (audit_anchorexterno + SSO + license pooling + shared_skills_repo);
max_evals_datasets: CAP_UNLIMITED en todos los tiers (no se paywallea
Evals · regla retention engine); rename
reliability_dashboard_full → behavior_confidence_full (consistente
con backend); max_controllers → max_registered_controllers (la
semántica es "instalaciones controller-capable", no "controllers
concurrentes" — siempre 1 activo por INV-Core-1).
max_registered_controllers=3 no unlimited; audit_anchor_external
solo en Enterprise).
handoff_router._aleph_emit llamaba `AlephService.integrate(agent_id=...,
target=..., category=..., payload=...)` pero la signature real es
integrate(ws: AgentWorkspace, target_ref: str). El TypeError resultante
se tragaba en except Exception: pass, así que los 4 eventos de PeerHandoff
(requested/accepted/completed/rejected) **nunca llegaron al ledger
Aleph**. Fix:
AlephService.record_handoff_event(event, payload) (sibling de record_skill_event, mismo patrón GICS emit).
_aleph_emit reescrito para usar el método correcto + log warning real al fallo (antes era pass silente, que es lo que ocultó el bug
durante meses).
Tras investigación de técnicas de ahorro de tokens en Claude Code (8
fuentes 2026 + docs oficiales):
AGENTS.md § Token Discipline con reglas operacionalesobligatorias en sesiones >5 archivos o >30 min.
~/.claude/projects/<proj>/memory/reference_token_savings.md con las 3
técnicas de mayor ROI (prompt caching, subagentes para verbose,
Read/Grep con límites) + tabla de antipatrones.
/token-discipline (~/.claude/skills/token-discipline/SKILL.md)para auditar sesión activa contra patrones malos en 4 ejes.
Post-multi-agent analysis (3 specialists + synthesizer/devil's advocate
with corrected sector framing — GIMO = multi-agent orchestrator control
plane, not AI tool):
solo → operator, pro → mesh. Names anchor thedifferentiators (control plane operator + distributed mesh) instead of
generic "Solo/Pro" SaaS noise. LicenseTier union, TIER_DEFS,
validation, all consumers updated.
Aligned to multi-agent sector (LangSmith $39/seat, Letta $20-99,
AgentOps $20-99, Dust $29) — not to AI tools (Cursor/Copilot).
Replaces additive €15/seat over Pro that forced mental math (Linear /
Notion / Vercel pattern).
audit_chain_local): declarative flag thatsurfaces the only differentiator no OSS multi-agent framework
(AutoGen, LangGraph, CrewAI, Letta) ships. Backend audit chain has
always been server-wide (ENABLE_AUDIT_CHAIN_V2); the flag makes the
inclusion visible in /pricing without changing runtime behavior.
tier === "founding_lifetime" returns 410 Gone in /api/billing/checkout POST + 302 redirect on GET.
UI card removed from /pricing. Stripe product founding_lifetime_v1
remains in test mode pending manual archive (no customers
pre-revenue). Replacement Founding Annual €119 cap 100 under
evaluation contingent on ≥100 emails in 6-week waitlist; otherwise
plan B (no founder tier).
POST /api/waitlist via sendIfPermitted (template public-waitlist). Fail-safe: signup
succeeds even if email fails. Dedupe silent (no spam on re-signup).
<WaitlistForm> client component(subdued, source-tagged). Tone confident ("te avisaremos cuando tu
acceso esté autorizado"), no counter visible (anti growth-hacky in
dev culture).
GET /api/admin/waitlist (JSON · count, last7d/24h velocity, source breakdown, last 20 signups, % to threshold)
+ visual dashboard at /account/admin/waitlist with progress bar to
the kill-switch threshold (100).
agentes IA" + sub explicit about BYO inference. Audit chain
dedicated card. Tier comparison table vs AutoGen+DIY / LangSmith
Cloud / Cursor Pro (10 capabilities scored). Enterprise card
"Contactar" as Ariely decoy + compliance lead capture.
(operator_monthly_v2, operator_yearly_v2, mesh_monthly_v2,
mesh_yearly_v2, team_base_v2, team_seat_addon_v2). Legacy
STRIPE_PRICE_SOLO_*/PRO_* env aliases retained in stripe-price-map.ts
fallback for safe transition.
with new tier names + new backward-compat alias coverage.
Files touched: `lib/licensing/{tier-defs,schema,stripe-price-map,index,
__tests__/*}.ts, app/api/{billing/checkout,waitlist,admin/waitlist,
webhooks/stripe}/route.ts, app/pricing/page.tsx`,
components/pricing/WaitlistForm.tsx, app/account/admin/waitlist/page.tsx,
scripts/{create-stripe-products,migrate-licenses}.ts,
tools/gimo_server/security/audit_emitter.py (docstring only).
See ADR-0007 for the full rationale.
Largest single-day cluster to date: 14 commits on main + 3 on
context-engine, ~6000 LOC added, 353 tests passing. Productizes
auth/license/subscription end-to-end after a multi-block research
phase (35 agents · 10 innovation candidates × 5 vulnerabilities × 4 UX
patterns × 3 cost estimates) and a separate context-engineering
research (4 deeper agents on academic SOTA, industry landscape,
specialized formats, and multi-turn delta encoding).
Security · P0 + supply-chain
aud="gimo-orch" strict validation closesthe confused-deputy hole (CVE-2018-0114 pattern) in 19 CLI endpoints.
Pre-Capa-1 legacy bonds without aud are rejected on deploy; users
re-bond via gimo login --web. (fix(security): B1 · 8 regression
tests in test_bond_issuer.py + test_credential_facade.py.)
processed_events withatomic Firestore transactions closes the C-Squad2 PoC#1 replay
window across Stripe, Resend, SMTP2GO. SMTP2GO event-id derived from
sha256(body) since no per-event id is issued. 12 unit tests including
race-condition with Promise.all. Firestore TTL policy on
ttlUntil must be enabled manually (gcloud command in helper header).
releases. Coexists with legacy release.yml. Public Rekor log;
verification documented in docs/security/VERIFY_RELEASES.md.
AuthContext.subscription_status field + enforce_method_scope downgrade to read-only for
cancelled/expired/revoked/paused statuses (Innov 7 backbone).
Default "active" preserves backward compatibility. 18 new tests.
orchestrator-cert (signs device certs) and seat-proof (signs
seat_proofs), separate KV namespaces, separate signing keys. €0 within
CF free tier. Not deployed; ready for wrangler deploy.
(Tailscale TKA style). Behind ENABLE_AUDIT_CHAIN_V2 feature flag,
zero regression when off. Notary hook for E5 (deferred). 22 new tests
· full security suite 238/238 green. See docs/architecture/AUDIT_CHAIN.md.
Licensing · schema v2 with tier+caps+features
licenses/{uid} v2 schema with 5 tiers (free, solo,pro, team, internal_dev), hard caps + feature flags, Janus locked to
0 sessions across all commercial tiers per the Janus dev-only
decision. Migration script migrate-licenses.ts with dry-run by
default, v1→v2 idempotent transformer. 81 new tests. See
apps/web/docs/LICENSE_SCHEMA.md and ADR-0005.
buildLicenseDoc(tier, ...) instead of hardcoded `plan="standard"
+max=2. Mapping price_id → tier via STRIPE_PRICE_MAP` env JSON.
Stripe status → LicenseStatusV2 mapping with past_due → active
grace and pause_collection != null → paused for F1.
Dual-write v1+v2 during coexistence. Fire-and-forget seat-proof
worker call with HMAC auth.
/pricing page rendering 4 tiers + Founding Lifetime card from TIER_DEFS. Single source of truth · changing a cap in
tier-defs.ts updates the page automatically.
controller vs mesh_node caps. Apple Music / Spotify TV style "device limit
reached" message with device labels + last-seen. 60-day inactivity
GC helper. 18 tests.
Identity · OAuth + WebAuthn
giltech.dev for cross-subdomain support. @simplewebauthn/server v13.
Step-up cookie HMAC-signed with 30-day freshness. Recovery flow with
15-min single-use magic link. StepUpGate overlay enforces
registration on first sign-in. New Firestore collections
webauthn_credentials/, webauthn_challenges/,
webauthn_recovery/. 9 new tests. Docs in apps/web/docs/WEBAUTHN.md.
(apps/auth-hosting) alongside Google. Affects entire Gred In Labs
ecosystem (GIMO + GICS + GISH + Mesh). Scopes: read:user,
user:email. buildGithubProvider + startGithubSignIn mirror
Google's API. ProviderDivider between buttons. i18n keys in es/en.
GET /api/account/devices + POST /api/account/devices/[id]/revoke
with ownership chain check (activation → license → user uid).
Lockout guard refuses to revoke the only active device. Current-
device confirmation requires typed REVOKE. UI at /account/devices
with Apple Music / Spotify TV bulk-revoke pattern. NO email
magic-links (anti-phishing per P3 research).
Android · legal scope
commercial (release default) and internal (dev-only) product flavors. Removes the
AccessibilityService + Janus integration from the public APK and
isolates them to the internal flavor. Janus is dev-tool only per
ADR-0006. All 4 build targets pass assembleX (commercial+internal
× debug+test). See apps/android/gimomesh/FLAVORS.md.
Innovations · retention
pendiente (Stripe webhook → status flip cuando C2 está deployado;
rate-limit cancelled role per C-Squad4 recommendation).
Context Engine · separate context-engine branch (not in main)
context-engine with scaffold + benchmark v1 + benchmark v2.Honest disclosure: "60-70% combined savings" is partially validated
through integrated tokenization measurement, but the cache discount
itself is MODELED (60% mid-range from arXiv 2601.06007 "Don't Break
the Cache") not measured against live Anthropic/OpenAI API.
Benchmark v2 covers 3 workloads, 4 layers (L0/L1/L2/L3), sensitivity
bands at 41%/60%/80% cache discounts:
* Tabular-heavy short (10×20t): -73.4% at mid cache
* Mixed distribution (10×20t): -70.2% at mid cache
* Long session (3×100t): -77.8% at mid cache
* Surprise: L1 lazy tools is NEGATIVE (-2 to -4%) once L0 is in
place. Recommendation: skip L1.
* L3 JSON Patch deltas adds +4-8pp on top of L0+L2.
See tools/gimo_server/context_engine/benchmark/REPORT_v2.md for the
go/no-go criteria before merging anything to main.
Pending (not shipped, documented as known follow-ups)
on C1 ✅ — unblocked)
benchmark validation per the REPORT_v2.md go/no-go criteria)
Pre-deploy manual checklist for the founder (see journal entry
for the full step-by-step):
1. Firebase Console — enable GitHub provider (D2)
2. Stripe Dashboard — create real price IDs (Solo €9, Pro €19,
Team €15/seat, Founding Lifetime €129) + set STRIPE_PRICE_MAP
3. GCP Firestore — enable TTL policy on processed_events.ttlUntil (B2)
4. Cloudflare — generate 2 Ed25519 keypairs + 2 KV namespaces +
wrangler secret put × 2 + deploy 2 workers (E3)
5. Set SEAT_PROOF_WORKER_URL + SEAT_PROOF_WORKER_HMAC envs (C2)
6. Run apps/web/scripts/migrate-licenses.ts dry-run, then --commit
7. Optional: export ENABLE_AUDIT_CHAIN_V2=1 to activate E4 chain
Related: ADR-0005 (Licensing schema v2), ADR-0006 (Janus dev-only).
Full narrative + decision log: docs/journal/2026-05.md § entry
2026-05-21.
End-to-end email automation for SaaS B2B 1.0 paying-user readiness. Before
this session, the 4 transactional templates existed in code but were
NEVER wired to product events; the unsub URL pointed to a 404; and
billing webhook handlers updated DB state without sending any user-facing
email. After this session: 11 templates total (4 existing + 4 billing
new + 3 lifecycle drip new), full lifecycle wiring, CAN-SPAM + GDPR
compliance, bounce/complaint handling, open/click tracking. See
ADR 0004 for the
architectural rationale.
upsertUserOnLogin now returns {doc, isNewUser}; /api/auth/session fires
buildWelcomeEmail via sendIfPermitted("transactional") on first
sign-up. Idempotent via welcomeSentAt field. Failure NEVER blocks
login (try/catch silent + warn log).
POST /api/admin/users/[uid]/approve formalizes what was previously
manual Firestore-console editing. Idempotent (alreadyApproved if
re-clicked). Fires buildEarlyAccessApprovedEmail automatically.
/account/admin page lists approved==false users (limit 100) with
server-action approve button. Audit logged in sent_emails.
/account/notifications preferences page + public unsubscribe (T0.3) —4 categories (transactional locked, product, lifecycle, marketing).
GET/POST /api/account/notifications/preferences. Public
/unsubscribe?uid&category&token page accepts HMAC-SHA256 tokens
(verified timing-safe) and applies opt-out without requiring login —
CAN-SPAM compliant one-click unsub. Tokens never expire (regulation
requires permanent validity).
wired into the webhook handlers: public-payment-receipt (on
invoice.paid), public-payment-failed (on invoice.payment_failed),
public-trial-ending (on customer.subscription.trial_will_end —
new event handler), public-subscription-ended (on
customer.subscription.deleted). All via sendIfPermitted; failures
log + continue (NEVER throw to Stripe — would duplicate emails on
retry). All audited in sent_emails.
POST /api/webhooks/resend (svix-signature HMAC verification) and
POST /api/webhooks/smtp2go (X-SMTP2GO-Signature HMAC). On
email.bounced / email.complained / bounce / spam →
update users.emailStatus; sendIfPermitted then auto-skips
future automation to that recipient (preserves DKIM reputation).
Fail-closed (503) if RESEND_WEBHOOK_SECRET /
SMTP2GO_WEBHOOK_SECRET missing.
POST /api/cron/drip-tick (auth X-CloudScheduler-Token vs
CRON_TOKEN). Query users in ±12h window around each day target,
filtered by lifecycleStage to ensure each user receives day3 then
day7 in order, never both at once. Batch limit 50 per tick (Cloud
Run 60s timeout safety). Respects emailPreferences.lifecycle.
Three new templates: public-drip-day0/3/7. day0 is manual-only;
day3/day7 fire automatically via cron.
sendEmail() Resend payload now sets X-Track-Opens/X-Track-Clicks headers;
SMTP2GO payload sets track_opens/track_clicks: true. Provider
webhooks append events to email_events collection with
event, email_id, recipient, subject, link, timestamp.
sendIfPermitted wrapper (T1.3) — single chokepoint at lib/email/send-policy.ts enforcing preferences + bounce/complaint
status. Transactional category bypasses opt-out (CAN-SPAM/GDPR
permit billing/account comms unilaterally). Used by all automatic
email sites (session, stripe webhook, approve, drip-tick).
/account/admin/emails server-rendered view of sent_emails
last 30 days (limit 200). 4 metric blocks (by status, provider,
source, template) + table. No interactive filters (scope minimum).
/deadend [time] — autonomous time-boxed work skill installed at ~/.claude/skills/deadend/SKILL.md. First end-to-end
use was this session (/deadend 1h). Encodes the workflow:
plan-mode first, post shutdown command before approval, fire
shutdown /s /t <secs> as first Bash after approval, conservative
defaults (no --no-verify, no force-push), mandatory CHANGELOG +
journal + ADR before budget end. Final report template + risk
catalog. Lives in user home, not repo.
/account/compose now has two independent toggles: entity (Gred In
Labs / GIMO / GICS / GISH · changes header logo + descriptor +
subject prefix) and sender (josecarlos@ / equipo@ / gimo@ /
gics@ / gish@ / hola@ / soporte@ · changes the actual From:
alias). Default sender per entity; manual override available with
reset-to-default button.
end of compositor with a button that fires
POST /api/admin/test-transactional directly (no DevTools, no
fetch snippets). Result shown inline with provider, From header,
and Gmail "signed-by" verification hint.
giltech.dev; SMTP2GO transactional lane on mail.giltech.dev).
Split físico real with isolated DKIM. Documented in
giltech.dev — SPF includes _spf.resend.com; DMARC raised from p=none → p=quarantine pct=10
with reports to dmarc@. New DMARC record on _dmarc.mail.giltech.dev.
3 SMTP2GO CNAMEs (em650505.mail, s650505._domainkey.mail,
link.mail).
josecarlos@giltech.dev as personal alias — firstname@company outperforms generic aliases (hola@, team@) by
~6.8× reply rate in B2B outreach studies. Compositor default for
Gred In Labs entity.
cif: null without rendering an empty CIF line.
These commands must be run manually by the operator with appropriate
credentials; they are NOT executed by code:
1. Create Cloud Scheduler job for drip-tick cron:
```bash
gcloud scheduler jobs create http gimo-drip-tick \
--location=europe-west1 \
--schedule="0 * * * *" \
--time-zone="Europe/Madrid" \
--uri=https://gimo.giltech.dev/api/cron/drip-tick \
--http-method=POST \
--headers="X-CloudScheduler-Token=$(gcloud secrets versions access latest --secret=gimo-cron-token)"
```
Requires secret gimo-cron-token in Secret Manager (create with
gcloud secrets create gimo-cron-token --replication-policy=automatic,
then add version with a random 32-byte hex value).
2. Configure Resend webhook at https://resend.com/webhooks:
- Endpoint: https://gimo.giltech.dev/api/webhooks/resend
- Events: email.bounced, email.complained, email.opened,
email.clicked, email.delivered
- Copy the signing secret → store as RESEND_WEBHOOK_SECRET in
Secret Manager + add to cloudbuild.yaml --update-secrets.
3. Configure SMTP2GO webhook at https://app.smtp2go.com/settings/webhooks:
- Endpoint: https://gimo.giltech.dev/api/webhooks/smtp2go
- Events: bounce, hardbounce, spam, open, click, processed
- Copy the signing secret → store as SMTP2GO_WEBHOOK_SECRET in
Secret Manager + add to cloudbuild.yaml --update-secrets.
4. Add CRON_TOKEN, RESEND_WEBHOOK_SECRET, SMTP2GO_WEBHOOK_SECRET
to apps/web/cloudbuild.yaml --update-secrets list. Without these
the corresponding endpoints return 503 webhook_not_configured /
cron_not_configured (fail-closed by design — does not silently
accept unauthenticated calls).
5. Smoke-test post-deploy:
- Click "Disparar smoke test transaccional" in /account/compose →
receive welcome email in admin Gmail → check headers contain
signed-by mail.giltech.dev (proves DKIM split physical works).
- Visit /account/notifications → toggle marketing → check
Firestore users/{uid}.emailPreferences.marketing == false.
- Visit /account/admin → approve a pending early-access user →
check they receive the email.
(welcome trigger, drip-tick, webhooks, approve, preferences).
Reason: 1h budget. The Stripe webhook had existing tests covering
the DB updates; those still pass. Test design for new endpoints
(especially HMAC signature verification fixtures) needs the user.
emailPreferences on existing user docs. sendIfPermitted treats undefined prefs as conservative
defaults (transactional/product/lifecycle on, marketing off), so
existing users get the right behaviour lazily on first email.
Backfill optional if you want consistent docs.
follow-ups #1. Conservative decision (no headless gcloud creds in
agent context).
/account/admin/emails not interactive (no filters,search, pagination, CSV export). Scope intentional (T1.4 minimum).
Iterate when needed.
legacy_ui_router.py sunset (2026-05-20) — 349 LOC removed. Every /ui/* handler had a canonical replacement under /ops/* since the Palanca 2 audit (899f8248, 2026-04-19); the file lived on only as a source-based test anchor that asserted "if anyone re-mounts the router, the delegation pattern is preserved". The sunset criterion declared in the file's own docstring (docs/CLIENT_SURFACES.md no longer references /ui/* as live, API.md drift note in place, no live frontend calls) was satisfied. The router-table absence assertions in tests/unit/test_routes.py:259-285 remain as the canonical regression guard. See ADR 0002.cli_constants.py deleted — orphaned since the gimo_cli/ package decomposition (2026-04-01). All constants already lived in gimo_cli/config.py with corrected values (timeouts 180s vs stale 15s/30s, ACTIVE_RUN_STATUSES delegated to run_lifecycle.py including AWAITING_MERGE). Zero imports remained.services/trust.py TrustService deleted — dead prototype created alongside TrustEngine in the same commit (2026-03-31). Never imported, never tested. TrustEngine (trust_engine.py) is the canonical implementation with 7+ active consumers, full 3-state circuit breaker, per-dimension GICS config, and audit logging.services/ that forwarded to canonical sub-packages (ops/, execution/, economy/, workspace/, observability_pkg/, graph/). All ~193 consumer imports rewritten to canonical paths. Shims removed: ops_service, engine_service, sandbox_service, execution_policy_service, run_worker, cost_service, cost_predictor, budget_forecast_service, cascade_service, workspace_context_service, workspace_policy_service, repo_service, repo_override_service, repo_recon_service, observability_service, anomaly_detection_service, log_rotation_service, graph_engine.intent_classification_service.py used > 60 (risk=60 allowed review) while merge_gate_service.py used >= 60 (risk=60 blocked). Aligned to >= 60 everywhere, matching risk_calibrator.py baseline and existing test test_phase7_merge_gate_risk_60_is_hard_block._popen/_run in claude_auth_service.py and codex_auth_service.py (plus _run_sync in provider_catalog/_base.py) extracted to shared _subprocess_util.py. Windows .cmd shim compat and timeout handling in one place.services/observability.py (159-line MVP) that caused double JSONL writes and double metric counting alongside the canonical OTel-based observability_pkg/observability_service.py. Thread metadata accumulation (ConversationService.mutate_thread) moved inline to the agentic loop's unified telemetry block. Zero functional loss; 5 dead methods removed (record_agent_action, get_agent_insights, record_span, record_structured_event, record_usage).PHASE6_PRIMARY_MODEL / PHASE6_FALLBACK_MODEL class constants removed from ModelRouterService. Primary and fallback models now read from OpsConfig.phase6 (Pydantic config with JSON persistence). Forced-local intents reuse OpsConfig.auto_run_excluded_intents instead of a duplicated hardcoded set._infer_capabilities, _TIER_PATTERNS, _CAP_PATTERNS, _infer_weakness, _build_prior_scores, and prefer_family deleted across 12 files. GICS + benchmark enrichment is the single routing source of truth.~/.gimo/bonds/ with AES-256-GCM encryption~/.gimo/config.yaml for user-wide defaults with project overrides/ops/capabilities endpoint for CLI handshakegimo login <url>: Interactive bond creation with token prompt and server validationgimo logout <url>: Remove bond for a specific servergimo doctor: Comprehensive diagnostics with actionable hints (server, bond, license, config, git, provider)gimo providers login <provider>: Authenticate with LLM providers (codex/claude) via device flowgimo providers auth-status: Show authentication status for all configured providersgimo providers logout <provider>: Disconnect from an LLM providerGET /ops/capabilities: Returns server version, role, plan, and feature list for CLI bond handshake/status and /health now accessible to operator role (added to READ_ONLY_ACTIONS_PATHS)_resolve_token() rewritten: Now uses 6-level resolution chain with ServerBond integration_load_config() enhanced: Merges global config (~/.gimo/config.yaml) with project config (.gimo/config.yaml)_api_request() improved: Autorecovery for 401 (expired bond) and 503 (server unreachable) with user-friendly messagesstatus command: Works from any directory with env token or ServerBond (require_project=False)providers auth-status command: Works without project initialization (require_project=False)operator_status_service: Each subsnapshot (git, provider, thread, run, budget, alerts) wrapped in try/except to prevent cascade failures/status and /health endpoints now accessible without admin tokenUnicodeEncodeError on cp1252 consolesconfig parameter to two _resolve_token() calls (streaming + chat flows)gimo status now calls server when using env token or ServerBond, even outside project directoriesgimo providers auth-status works from any directory/ops/capabilities endpoint verified functional after server restartdocs/SERVERBOND_IMPLEMENTATION_REPORT.md: Complete architecture overview with SOTA analysisdocs/E2E_AUDIT_SUMMARY_2026-03-30.md: Executive summary with production readiness verdict (87.5% pass rate)docs/E2E_GAPS_FINAL_2026-03-30.md: Consolidated gap list (11 gaps + 2 observations)docs/E2E_AUDIT_ROUND2_2026-03-30.md: Round 2 comprehensive audit findingsdocs/E2E_VALIDATION_GAPS_2026-03-30.md: Round 1 validation findingstest_e2e_comprehensive.sh: Automated test suite for 32 critical endpoints/commandsdemo_e2e_serverbond.sh: Complete E2E demo script from init to logout~/.gimo/bonds/<fingerprint>.yaml with encrypted tokensgimo login <server_url> to create first bond.gimo login <url> for each server.gimo providers login codex to authenticate LLM providers.---
Previous features and changes (no changelog maintained prior to 0.9.2).