Registro público
Informe de salud del softwareesquema 0.27.0 · métricas 1.13.0 · 2026-07-28 06:42 UTC

runos-official / cli

RunOS command-line interface: manage clusters, deploy apps, and run services on your RunOS platform. Source-available (Elastic License 2.0).

GoLicencia propia★ 1 estrella⑂ 0 forksdesde dic 2025Ver en GitHub ↗

runos-official/cli tiene un índice de salud de 49 sobre 100, lo que lo sitúa en la banda En riesgo. Su puntuación más alta es Vitality (76/100) y la más baja, Community & Adoption (21/100). Se actualizó por última vez hace 11 días. Una sola persona concentra la mayor parte del trabajo reciente.

49
global / 100
En riesgo

Índice de salud del software

Las métricas se agrupan en categorías ponderadas sobre una escala de 1 a 100. El resultado global parte de su media; cuando la evidencia pública activa la Política de Jurisdicciones de Alto Riesgo, la calificación se ajusta y recibe el límite 49 (En riesgo). Preparación para IA queda fuera.

49
Excelente85-100Ejemplar; cumple prácticamente todos los criterios evaluados
Bueno70-84Saludable; carencias menores
Moderado50-69Aceptable con carencias notables; se recomienda revisión
En riesgo30-49Debilidades significativas; su adopción exige cautela
Crítico1-29Problemas graves (proyecto abandonado, un solo mantenedor, sin higiene)
VitalidadComunidad yAdopciónSostenibilidady GobernanzaCalidad deIngenieríaSeguridadPreparaciónpara IA

Perfil de puntuación

Cada eje es una categoría. La forma importa más que la media: un proyecto sano llena toda la figura, mientras que un perfil de picos y cráteres indica que la fortaleza en una dimensión enmascara el riesgo en otra.

Titularidad

RunOSOrganización
0 seguidores9 repositorios públicosdesde jun 2025

Este repositorio está respaldado por una organización: una custodia compartida y responsable que puede sobrevivir a cualquier mantenedor individual.

Ecosistemas de paquetes

RegistroPaqueteVersiónDescargas / mesVersionesÚltima publicación
Gogithub.com/runos-official/cliv1.12.2-48hace 11 días

Métricas por categoría

Vitalidad

¿Está vivo el proyecto: se escribe código y se publican versiones?

76Bueno · 22% del índice global
Cómo se puntúa
28.8/36Recencia de push — último push hace 11 días
9.7/36Cadencia de commits — 14/52 semanas con commits
18/18Volumen de commits — 154 commits en el último año
10/10OpenSSF Scorecard: Maintained — 30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10
Datos de entrada utilizados
commits_last_year154
human_commit_share1
days_since_last_push11
active_weeks_last_year14
Cómo se puntúa
27/27Publica versiones — 31 versiones publicadas
36/36Recencia de las versiones — última versión hace 11 días
27/27Cadencia de publicación — una versión cada ~1,8 días
0/10OpenSSF Scorecard: Signed-Releases — Project has not signed or included provenance with any releases.
Datos de entrada utilizados
releases_count31
latest_release_tagv1.12.2
releases_from_tagsno
days_since_latest_release11
mean_days_between_releases1,8

Comunidad y Adopción

¿Tiene el proyecto usuarios, descargas, atención y unas condiciones acogedoras para quienes contribuyen?

21Crítico · 18% del índice global
Cómo se puntúa
0/60Estrellas — 1 estrellas
0/25Forks — 0 forks
0/15Observadores — 1 observadores
Datos de entrada utilizados
forks0
stars1
watchers1
growth_stateunverified
growth_factor_pct100
growth_unverified_reasonno_history
Cómo se puntúa
22.5/22.5README
16.9/22.5Licencia — archivo de licencia presente, no es una licencia reconocida
0/18Guía CONTRIBUTING
0/13.5Código de conducta
0/7.2Plantilla de issues
0/6.3Plantilla de PR
Datos de entrada utilizados
has_readme
has_license
has_contributingno
has_issue_templateno
has_code_of_conductno
has_pull_request_templateno

Sostenibilidad y Gobernanza

¿Sobrevivirá el proyecto a sus personas: factor bus, capacidad de respuesta, quién lo respalda y mantenimiento del paquete?

33En riesgo · 24% del índice global
Cómo se puntúa
9/54Factor bus — la mitad de los commits recae en 1 contribuyente(s)
0/22.5Distribución de commits — el principal contribuyente firma el 100% de los commits
1.4/13.5Amplitud de contribuyentes — 1 contribuyentes
0/10OpenSSF Scorecard: Contributors — project has 0 contributing companies or organizations -- score normalized to 0
Datos de entrada utilizados
bus_factor1
contributors_sampled1
top_contributor_share1
Cómo se puntúa
0/46.8Resolución de issues — sin issues o sin datos
0/38.3Aceptación de PR — sin PR decididos o sin datos
0/15OpenSSF Scorecard: Code-Review — Found 0/30 approved changesets -- score normalized to 0
Datos de entrada utilizados
merged_prs0
open_issues0
closed_issues0
issue_closed_ratio
closed_unmerged_prs0
Excluidos de la puntuación (sin datos o no aplicable): Resolución de issues, Aceptación de PR. Los pesos restantes se han renormalizado.
Cómo se puntúa
30/30Respaldo de la propiedad — propiedad de una organización
0/20Dominio verificado
0/25Alcance del propietario — 0 seguidores de runos-official
9.6/25Trayectoria — 9 repos públicos, cuenta de ~1 años
Datos de entrada utilizados
followers0
owner_typeOrganization
is_verified
owner_loginrunos-official
public_repos9
account_age_days418
Cómo se puntúa
25/25Publicado y resoluble — 1 paquete(s) en go
35/35Recencia de publicación — última publicación hace 11 días
20/20Historial de versiones — 48 versiones en el registro
20/20No obsoleto — activo, ni obsoleto ni retirado
Datos de entrada utilizados
packagesgithub.com/runos-official/cli
ecosystemsgo
any_deprecatedno
min_days_since_publish11

Calidad de Ingeniería

¿Existen unas prácticas mínimas de ingeniería y documentación?

66Moderado · 20% del índice global
Cómo se puntúa
24/24Flujos de trabajo de CI — 2 flujo(s) de trabajo
24/24Pruebas presentes
0/16Configuración de linter
0/9.6Hooks de pre-commit
0/6.4.editorconfig
0/20OpenSSF Scorecard: CI-Tests — sin datos
Datos de entrada utilizados
has_ci
has_tests
has_editorconfigno
has_linter_configno
has_precommit_configno
Excluidos de la puntuación (sin datos o no aplicable): OpenSSF Scorecard: CI-Tests. Los pesos restantes se han renormalizado.
Cómo se puntúa
30/30README
0/25Directorio de documentación
15/15Sitio de documentación / página del proyecto — https://runos.com
10/10Descripción del repositorio
10/10Topics — 4 topics
10/10Wiki
Datos de entrada utilizados
topicscli, kubernetes, runos, source-available
has_wiki
homepagehttps://runos.com
has_readme
has_docs_dirno
has_description

Seguridad

¿Son sólidas las prácticas visibles de seguridad y de cadena de suministro, sin exposición jurisdiccional de alto riesgo sin resolver?

45En riesgo · 16% del índice global
Cómo se puntúa
7.5/7.5Binary-Artifacts — no binaries found in the repo
0/7.5Branch-Protection — branch protection not enabled on development/release branches
0/2.5CI-Tests — sin datos
0/2.5CII-Best-Practices — no effort to earn an OpenSSF best practices badge detected
0/7.5Code-Review — Found 0/30 approved changesets -- score normalized to 0
0/2.5Contributors — project has 0 contributing companies or organizations -- score normalized to 0
10/10Dangerous-Workflow — no dangerous workflow patterns detected
0/7.5Dependency-Update-Tool — no update tool detected
0/5Fuzzing — project is not fuzzed
2.2/2.5Licencia — license file detected
7.5/7.5Maintained — 30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10
0/5Packaging — sin datos
5/5Pinned-Dependencies — all dependencies are pinned
0/5SAST — no SAST tool detected
5/5Security-Policy — security policy file detected
0/7.5Signed-Releases — Project has not signed or included provenance with any releases.
0/7.5Token-Permissions — detected GitHub workflow tokens with excessive permissions
6.8/7.5Vulnerabilities — 1 existing vulnerabilities detected
Datos de entrada utilizados
sourceopenssf_scorecard
checks_evaluated16
scorecard_versionv5.5.0
checks_inconclusive2
scorecard_aggregate4,5
Excluidos de la puntuación (sin datos o no aplicable): ci_tests, packaging. Los pesos restantes se han renormalizado.

Preparación para IA

¿Hasta qué punto está el repositorio preparado para desarrollarse y mantenerse con agentes de codificación de IA? Es una insignia independiente y experimental — peso 0,0, de modo que se presenta por separado y no afecta a la puntuación de salud global.

64Moderado · 0% del índice global
Cómo se puntúa
0/45Instrucciones para agentes — sin CLAUDE.md / AGENTS.md / reglas de editor
0/15Documentación legible por máquinas (llms.txt)
40/40Historial de commits legible — 98 de 100 commits humanos declaran su intención (asunto estructurado o cuerpo explicativo)
Datos de entrada utilizados
has_llms_txtno
legible_history_share0,98
agent_instruction_files
agent_instruction_max_bytes
Cómo se puntúa
18/18Arranque con un solo comando — Makefile
22/22Pruebas automatizadas
0/11Configuración de lint / formato
11/11Verificación estática de tipos — Go (tipado estático)
10/10Entorno reproducible — lockfile
0/10Práctica demostrada con agentes — ningún commit con autoría de agente entre los últimos 100
0/8Mantenimiento automatizado — no se observan actualizaciones automáticas de dependencias
10/10OpenSSF Scorecard: Pinned-Dependencies — all dependencies are pinned
Datos de entrada utilizados
has_nixno
has_tests
lockfilesgo.sum
has_dockerfileno
typed_language
bootstrap_filesMakefile
has_devcontainerno
has_linter_configno
typecheck_configs
agent_commit_share0
toolchain_manifestsgo.mod
dependency_bot_commit_share0
Cómo se puntúa
45/45Código verificable por tipos — Go (tipado estático)
51.3/55Tamaños de archivo manejables — 9/133 archivos fuente de más de 60 KB
Datos de entrada utilizados
primary_languageGo
largest_source_bytes95.672
source_files_sampled133
oversized_source_files9

Datos clave

1estrellas de GitHub
1contribuidores
154commits en los últimos 12 meses
11días desde el último push
31versiones publicadas
1factor bus
0issues abiertas
Goecosistemas de paquetes

Advertencias de recopilación de datos

  • Star history unavailable: GitHub GraphQL error: Resource not accessible by personal access token
  • GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository

Más detalle

OpenSSF Scorecard 4.5 / 10
4.5agregado

Evaluación de seguridad independiente y agnóstica en cuanto a herramientas, procedente del proyecto de código abierto OpenSSF Scorecard. Cada comprobación premia una práctica de seguridad, no la herramienta de un proveedor concreto. Las comprobaciones que Scorecard no pudo determinar se marcan como n/d y se excluyen de la puntuación de seguridad (nunca se cuentan como cero).Scorecard v5.5.0 · 2026-07-28 06:42 UTC

10Binary-Artifactsno binaries found in the repo
0Branch-Protectionbranch protection not enabled on development/release branches
n/dCI-Testsno pull request found
0CII-Best-Practicesno effort to earn an OpenSSF best practices badge detected
0Code-ReviewFound 0/30 approved changesets -- score normalized to 0
0Contributorsproject has 0 contributing companies or organizations -- score normalized to 0
10Dangerous-Workflowno dangerous workflow patterns detected
0Dependency-Update-Toolno update tool detected
0Fuzzingproject is not fuzzed
9Licenselicense file detected
10Maintained30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10
n/dPackagingpackaging workflow not detected
10Pinned-Dependenciesall dependencies are pinned
0SASTno SAST tool detected
10Security-Policysecurity policy file detected
0Signed-ReleasesProject has not signed or included provenance with any releases.
0Token-Permissionsdetected GitHub workflow tokens with excessive permissions
9Vulnerabilities1 existing vulnerabilities detected
Dependencias directas 5
RegistroPaqueteRestricción de versiónManifiesto
Gogithub.com/pmezard/go-difflibv1.0.0go.mod
Gogithub.com/spf13/cobrav1.10.1go.mod
Gogithub.com/spf13/pflagv1.0.9go.mod
Gogolang.org/x/termv0.41.0go.mod
Gogopkg.in/yaml.v3v3.0.1go.mod
Todas las dependencias no recopilado

No fue posible recopilar el conjunto de dependencias resuelto para este informe: GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository

Informe JSON sin procesar legible por máquina
{
  "data": {
    "repo": {
      "topics": [
        "cli",
        "kubernetes",
        "runos",
        "source-available"
      ],
      "is_fork": false,
      "size_kb": 4613,
      "has_wiki": true,
      "homepage": "https://runos.com",
      "languages": {
        "Go": 2000340,
        "Shell": 13194,
        "Makefile": 2705
      },
      "pushed_at": "2026-07-16T19:08:17Z",
      "created_at": "2025-12-23T12:50:01Z",
      "owner_type": "Organization",
      "updated_at": "2026-07-16T19:08:25Z",
      "description": "RunOS command-line interface: manage clusters, deploy apps, and run services on your RunOS platform. Source-available (Elastic License 2.0).",
      "is_archived": false,
      "is_disabled": false,
      "license_spdx": null,
      "default_branch": "main",
      "license_spdx_raw": "NOASSERTION",
      "primary_language": "Go",
      "significant_languages": [
        "Go"
      ]
    },
    "owner": {
      "blog": "https://runos.com",
      "name": "RunOS",
      "type": "Organization",
      "login": "runos-official",
      "company": null,
      "location": "United States of America",
      "followers": 0,
      "avatar_url": "https://avatars.githubusercontent.com/u/214831878?v=4",
      "created_at": "2025-06-04T12:44:53Z",
      "is_verified": null,
      "public_repos": 9,
      "account_age_days": 418
    },
    "license": {
      "state": "custom",
      "spdx_id": null,
      "raw_spdx": "NOASSERTION",
      "file_present": true,
      "scorecard_found": true,
      "profile_has_license": true
    },
    "activity": {
      "releases": [
        {
          "tag": "v1.12.2",
          "kind": "patch",
          "published_at": "2026-07-16T18:50:56Z"
        },
        {
          "tag": "v1.12.2-rc.1",
          "kind": "prerelease",
          "published_at": "2026-07-16T18:43:32Z"
        },
        {
          "tag": "v1.12.1",
          "kind": "patch",
          "published_at": "2026-07-10T08:22:44Z"
        },
        {
          "tag": "v1.12.1-rc.1",
          "kind": "prerelease",
          "published_at": "2026-07-10T08:18:57Z"
        },
        {
          "tag": "v1.12.0",
          "kind": "minor",
          "published_at": "2026-07-08T19:47:03Z"
        },
        {
          "tag": "v1.12.0-rc.1",
          "kind": "prerelease",
          "published_at": "2026-07-08T07:59:31Z"
        },
        {
          "tag": "v1.11.3",
          "kind": "patch",
          "published_at": "2026-07-06T10:34:50Z"
        },
        {
          "tag": "v1.11.3-rc.1",
          "kind": "prerelease",
          "published_at": "2026-07-06T10:31:18Z"
        },
        {
          "tag": "v1.11.2",
          "kind": "patch",
          "published_at": "2026-06-30T12:02:17Z"
        },
        {
          "tag": "v1.11.2-rc.1",
          "kind": "prerelease",
          "published_at": "2026-06-30T11:47:47Z"
        },
        {
          "tag": "v1.11.1",
          "kind": "patch",
          "published_at": "2026-06-29T18:43:03Z"
        },
        {
          "tag": "v1.11.0",
          "kind": "minor",
          "published_at": "2026-06-18T07:57:24Z"
        },
        {
          "tag": "v1.10.1",
          "kind": "patch",
          "published_at": "2026-06-17T12:54:23Z"
        },
        {
          "tag": "v1.10.0",
          "kind": "minor",
          "published_at": "2026-06-11T10:30:13Z"
        },
        {
          "tag": "v1.9.0",
          "kind": "minor",
          "published_at": "2026-06-09T18:19:11Z"
        },
        {
          "tag": "v1.8.0",
          "kind": "minor",
          "published_at": "2026-06-08T14:40:53Z"
        },
        {
          "tag": "v1.7.5",
          "kind": "patch",
          "published_at": "2026-06-08T11:00:11Z"
        },
        {
          "tag": "v1.7.4",
          "kind": "patch",
          "published_at": "2026-06-08T07:20:46Z"
        },
        {
          "tag": "v1.7.3",
          "kind": "patch",
          "published_at": "2026-06-07T16:30:31Z"
        },
        {
          "tag": "v1.7.2",
          "kind": "patch",
          "published_at": "2026-06-06T16:33:39Z"
        },
        {
          "tag": "v1.7.1",
          "kind": "patch",
          "published_at": "2026-06-06T13:43:29Z"
        },
        {
          "tag": "v1.7.0",
          "kind": "minor",
          "published_at": "2026-06-06T11:14:07Z"
        },
        {
          "tag": "v1.6.0",
          "kind": "minor",
          "published_at": "2026-06-05T10:05:30Z"
        },
        {
          "tag": "v1.5.0",
          "kind": "minor",
          "published_at": "2026-06-04T17:23:30Z"
        },
        {
          "tag": "v1.4.2",
          "kind": "patch",
          "published_at": "2026-06-03T16:38:30Z"
        },
        {
          "tag": "v1.4.1",
          "kind": "patch",
          "published_at": "2026-06-03T09:17:13Z"
        },
        {
          "tag": "v1.4.0",
          "kind": "minor",
          "published_at": "2026-06-03T08:18:14Z"
        },
        {
          "tag": "v1.3.0",
          "kind": "minor",
          "published_at": "2026-06-03T08:18:23Z"
        },
        {
          "tag": "v1.2.0",
          "kind": "minor",
          "published_at": "2026-06-03T08:18:10Z"
        },
        {
          "tag": "v1.1.0",
          "kind": "minor",
          "published_at": "2026-06-01T15:38:38Z"
        },
        {
          "tag": "v1.0.0",
          "kind": "major",
          "published_at": "2026-05-31T17:11:23Z"
        }
      ],
      "recent_commits": [
        {
          "oid": "8481c009241283058aa3a22aa1da703995385ce2",
          "body": null,
          "is_bot": false,
          "headline": "docs(changelog): v1.12.2 (apps_sync allowDrop)",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-07-16T18:40:48Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "ac56a34dc1fc9d26459f0b0876fead8c7d1d5624",
          "body": "apps_sync is a declarative full replacement computed from local files, so keys\nthe server has but the local set lacks are intentional deletions (already in the\nsync plan's Remove list). Conductor's partial-drop guard (now live on prod as\n1.9.0) would otherwise 400 those legitimate removals; the guard targets an\naccidental partial `set`, not this path. Sets allowDrop on ReplaceSecretEnvVars\nand UpdateSecrets.",
          "is_bot": false,
          "headline": "fix(apps): send allowDrop on apps_sync secret/env full-replace",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-07-16T18:40:09Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "a9f94b1b960fd3029383ea28d65c431ec9c7fc9d",
          "body": "runos status now enriches its output when signed in: company name and\nwebsite from GET /:aid/account/profile, and the default cluster's\ndisplay name resolved via GET /:aid/clusters (rendered as 'name (cid)',\nor '(not found on this account)' for a stale default cid). The\nEnvironment line is removed from the text output; --json keeps\nenvironment and adds companyName, website, defaultClusterName and\ndefaultClusterMissing. Enrichment is best-effort (10s timeout) so\nstatus still works offline.",
          "is_bot": false,
          "headline": "feat(status): show company profile and cluster name, drop env line",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-07-10T08:15:56Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "308b31177cbe6c8c2526ee79ea6366a5a42d5e44",
          "body": "platform-overview + one task topic is enough context before other\ntools unlock; the bootstrap topic router pulls further reads in via\nsee-also links. Cuts per-session token + round-trip overhead.",
          "is_bot": false,
          "headline": "feat(mcp): lower read-server topic gate from 3 to 2 topics",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-07-08T07:56:33Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "0424ac1aee0a059f4e85e410a43f82c6ab93a3fd",
          "body": "…r field)\n\nA server port mapping without standardHttps (legacy docs) zero-valued to\nfalse on pull, so a pull -> deploy round-trip persisted standardHttps:\nfalse and silently moved the app off standard-HTTPS routing (test session\n2026-07-06, app dx8jw). Port.StandardHttps is now *bool with the platform\ndefault (true) resolved at every read via StandardHTTPSValue(); drift\ncompare and the sync wire payload resolve the same default on both sides.",
          "is_bot": false,
          "headline": "fix(apps): default missing standardHttps to true in pull/sync (pointe…",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-07-06T10:28:30Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "2137bbb7c8d0db0773bf9ba23e4c4e91362a35d4",
          "body": "…script + CI notes)",
          "is_bot": false,
          "headline": "fix(release): map -rc.N tags to their base vX.Y.Z CHANGELOG section (…",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-30T11:45:10Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "f023ad65fdee2e8044e33c264c2b057045a556a7",
          "body": "…efault env files",
          "is_bot": false,
          "headline": "docs(changelog): v1.11.2 fix second-deploy failure on round-tripped d…",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-30T11:43:36Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "8ee23902fb7a2299bd603c194eda1ba4c6ec007e",
          "body": "…-overrides-convergence-)",
          "is_bot": false,
          "headline": "merge test_session 67 (fm/ts-67-verify-migration-ux-report-deploy-env…",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-30T08:43:48Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "f11d0b72a895aadcaf74637a5687b6eab76c1113",
          "body": "…t deploy\n\nFirst deploy auto-derives and persists secretEnv: .runos.<cid>.<id>.env into\nrunos.yaml. On the second deploy ResolveEnvFiles read that persisted field\nback as explicit, so VerifyExplicitEnvFiles (aef934f) hard-failed on a\ngitignored secret file the CLI itself referenced but never created\n[…]\nore the check whenever the server actually has env vars.\n\nRegression: TestDeployTwiceRoundTrip_DefaultSecretEnvExempt + ResolveEnvFiles\nsubtests (fail pre-fix with the exact bug error, pass post-fix).",
          "is_bot": false,
          "headline": "fix(deploy): persisted default secretEnv/env no longer breaks the nex…",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-30T08:24:45Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "c917df85a8daf4ae44df86e47b1d682dd8a5313f",
          "body": null,
          "is_bot": false,
          "headline": "docs(changelog): v1.11.1",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-29T18:39:53Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "3f8f9411944dcc62e01384b24593cac57c4e28eb",
          "body": "…-allowlist-silently-dis)",
          "is_bot": false,
          "headline": "merge test_session 64 (fm/ts-64-app-deploy-vcs-env-file-resolution-ip…",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-25T18:54:12Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "aef934f2fb51a4fe1dcd936a4efe64cc4e0a81cd",
          "body": "…missing\n\nBug 102 (ts-64), CLI slice (defect b). An explicit `env:`/`secretEnv:`\nreference naming a non-existent file silently deployed EMPTY env — e.g.\nwiping ALLOWED_CIDRS and disabling an app's in-app source-IP allowlist\nwith no error. `deploy.LoadEnvFile` and `apps.LoadLocalEnv` both treated\nos.\n[…]\n both paths.\n\nNot in scope: defect (a), VCS env path resolution against clone-root, is\nserver-side (conductor + cluster agent); the CLI VCS path sends only\n{sha, configPath} and never reads env files.",
          "is_bot": false,
          "headline": "fix(deploy/sync): fail loud when an explicit env:/secretEnv: file is …",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-25T15:35:14Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "c12698787ba8013e754e7da99e8c4b10b16b2ebf",
          "body": "…ous delete\n\nnodes/delete is conditionally async: it returns a jobId only with\n--delete-cloud-instance (the removeServer job). The plain delete completes\nsynchronously and returns {success, nid} with no job, but --follow blindly\ncalled followJob and errored 'response does not contain jobId' on a delete that\nactually succeeded. Now --follow only engages when the response carries a real\njobId; otherwise it renders the synchronous result normally. Regression test on\nresponseHasJobID.",
          "is_bot": false,
          "headline": "fix(nodes delete): --follow no longer errors on a successful synchron…",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-21T08:37:26Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c7ac5572d0d81832c0bdcdb6e80795a2ac832b5d",
          "body": "…t repos)\n\n- Add LICENSE (Elastic License 2.0, source-available) + NOTICE, and a License\n  section in the README. RunOS is proprietary; the source is published for\n  transparency, not as open source.\n- Stop tracking .claude/ and CLAUDE.md (local AI-assistant state that also\n  documented internal release/process); gitignore them, matching the cluster\n  and node agent repos.",
          "is_bot": false,
          "headline": "Add Elastic License 2.0 + drop internal agent files (parity with agen…",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-20T11:06:58Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "7f687f6ce80f7bb2d60342be14611a1ea4260281",
          "body": "…new capability",
          "is_bot": false,
          "headline": "docs(release-cli): default version bumps to patch; reserve minor for …",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-18T07:59:24Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "ee4b744414a959584454f982888dd4e21e24a3bd",
          "body": "…; warm manifest after login\n\nFresh install no longer prints 'Unable to load manifest' / 'failed to fetch\nCLI manifest on first run' before login. Distinguishes not-signed-in from\nbroken-setup via an auth.ErrNotAuthenticated sentinel + network-free\nauth.HasCredentials. Bare 'runos' shows a welcome; signed-out commands get a\nlogin nudge; manifest is warmed on login success.",
          "is_bot": false,
          "headline": "feat(first-run): friendly welcome + suppress pre-login manifest noise…",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-18T07:54:08Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "134247e1806382dfb363674b4ade59568cd6d3ef",
          "body": null,
          "is_bot": false,
          "headline": "docs(changelog): v1.10.1 restore self-update for pre-1.7.0 binaries",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-17T12:50:55Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "92dcd1d764ec7038b25f87939eca541ffc1ce3bf",
          "body": "…7.0 updaters\n\nBinaries v1.0.0-v1.6.0 hardcode a runos-latest-{os}-{arch} asset name in\ntheir self-updater download + checksum-lookup paths. Commit 5ec7510\n(shipped v1.7.0) renamed the canonical asset to runos-{os}-{arch}, so those\nold binaries have 404'd on every `runos update` since. Publish a\nbyte-identical copy under the legacy name (created before the checksum and\nattestation steps so both cover it) to let stuck old clients self-update to\ncurrent. Drop once no pre-1.7.0 clients remain.",
          "is_bot": false,
          "headline": "ci(release): publish runos-latest-{os}-{arch} alias assets for pre-1.…",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-17T12:20:48Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "64c055087bbe6f04585b0cfcda0fb079be177d2c",
          "body": "…deploymentStrategy sync clobber; reject inline envVars in runos.yaml; print deploy advisory warnings",
          "is_bot": false,
          "headline": "feat(apps): nodeAffinityTags round-trip (pull/diff/sync/deploy); fix …",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-11T10:27:00Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "7d8de3713808dbb320a48e883d4d6b74ace553e3",
          "body": null,
          "is_bot": false,
          "headline": "docs(release-cli): document foreman advertise_version step",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-09T18:04:52Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "14c446c9b943050512abbf47f7556299cd889fe4",
          "body": "…manifest pins",
          "is_bot": false,
          "headline": "docs(changelog): v1.9.0 postgres drop-* destructive guard + buildkit …",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-09T18:04:31Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "1b3c8a02d00111b07de64fdda54f9538f1e077de",
          "body": "Objective 60 story 110: verify the BuildKit set-advanced-configs surface\nis fully manifest-driven end-to-end. No production code change needed;\nthe generic dynacmd + MCP path handles the 17 scalar knobs as shipped in\nconductor manifest 28.24.0 (story 108).\n\n- internal/dynacmd/buildkit_advanced_test.\n[…]\nnt, id required, integer->number, and\n  structured enum surfaced on snapshotter/logLevel/logFormat. Defaults\n  are prose per the house pattern (no sibling set-advanced-configs\n  structures a default).",
          "is_bot": false,
          "headline": "test(buildkit): pin set-advanced-configs manifest surface (CLI + MCP)",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-09T16:32:07Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "65cf519ca8b003f42acfb7d2b8a1d688b38dee11",
          "body": "…e guard\n\nAdd 'drop-' to destructiveVerbPrefixes so the new sync-PATCH teardown\nverbs (services/postgresql/{id}/drop-user, /drop-database) inherit the\nuniform destructive guard: --yes/-y registration + y/N prompt on a TTY,\nnon-TTY refusal without --yes. Prefix match is method-agnostic and\nfuture-pro\n[…]\nthat\nformatDependentsError already renders verbatim. MCP exposure is the\nmanifest's automatic registration (no skip-set entry).\n\nRegression tests pin both verb paths as destructive (obj-58 Story 103).",
          "is_bot": false,
          "headline": "feat(dynacmd): gate postgres drop-user/drop-database under destructiv…",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-08T17:45:50Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "e7fdbf72b07d9aa02ae88e7650f09e24867d30ff",
          "body": null,
          "is_bot": false,
          "headline": "docs(changelog): v1.8.0 services postgresql clone-database command",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-08T14:33:29Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "6aa1366b5a2252d2c3ffd8c45ed7feb59ef8c932",
          "body": "…comment\n\nKeep flagNameFor's acronym-rule doc comment directly attached to the\nfunction. The override var + its rationale now sit above, so godoc\nattributes each comment to the right symbol. No behavior change.",
          "is_bot": false,
          "headline": "refactor(dynacmd): place flagSpellingOverrides above flagNameFor doc …",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-08T14:33:12Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "4131ed0b3b5e8b4b00456c351ecf122275798f2c",
          "body": "…ssion tests\n\nservices/postgresql/clone-database is manifest-driven (no static cobra\ncode). Conductor fixed the wire body keys (sourcePgOsid / sourceDatabase /\ntargetDatabase) and delegated flag presentation to the CLI: a naive kebab\nof those names gives --source-pg-osid / --source-database / --targ\n[…]\ned from the body while the five discrete fields stay\n  top-level, and sourceCid is NOT mistaken for the ambient :cid slot.\n- TestCloneDatabaseAutoFollow: pin that the jobId output auto-wires --follow.",
          "is_bot": false,
          "headline": "feat(dynacmd): clone-database flag-spelling overrides + mapping regre…",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-08T14:04:12Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "9b2006c11f71ad4d869b2f26922862a80aa9041c",
          "body": null,
          "is_bot": false,
          "headline": "docs(changelog): v1.7.5 PAT / RUNOS_API_KEY auth fixes (bugs 86, 87, 88)",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-08T10:57:03Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "bdf4f8ae69d1ec5b8814a9651c42a60bfda930d6",
          "body": "…llback (bug 88)\n\nresolveLoginAccountID fell back to the account_id already in config when\n--account-id was omitted under --api-key. A PAT is account-scoped, so\npairing a new PAT with a stale account id stored it against the wrong\ntenant: auth succeeds by token but every /:aid/... request targets th\n[…]\ntate it's required. CI env path\n(RUNOS_API_KEY + RUNOS_ACCOUNT_ID) pairs them explicitly and is untouched.\n\nRegression test asserts an absent flag errors instead of silently\ninheriting a config value.",
          "is_bot": false,
          "headline": "fix(login): require --account-id with --api-key, drop stale-config fa…",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-08T10:51:47Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "3dea938697a44298d322d5ac0831b5536dc60864",
          "body": "…ug 87)\n\nrunos status gated its auth check on cfg.RefreshToken != \"\" && cfg.Firebase\n!= nil, so a PAT-only config printed 'Not logged in' even though the PAT\nwas valid and every command worked. Extract resolveAuthMethod mirroring\nauth.ResolveToken's RUNOS_API_KEY -> stored PAT -> Firebase priority:\n\n[…]\nsh validation. Surface the method on the\nLogged-in line and an authMethod field in --json.\n\nTable-tested resolveAuthMethod across env/stored/firebase/none and the\nwhitespace-trim and nil-config edges.",
          "is_bot": false,
          "headline": "fix(status): recognise stored PAT / RUNOS_API_KEY as authenticated (b…",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-08T10:50:18Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "647ec26239f8e04ef4d94429ee964dfa1c38f373",
          "body": "getAuthToken hard-required cfg.Firebase and called GetIDToken directly,\nso a PAT-only config (api_key, no refresh token) failed every MCP tool\nvia the mcp_bootstrap gate, even though the same PAT worked for ordinary\nCLI commands. Route through auth.ResolveToken (RUNOS_API_KEY -> stored\nPAT -> Firebase priority), matching the dynacmd executor and the earlier\nupdater.go / jobs/service.go migrations off the same Firebase straggler.\n\nRegression test pins the PAT and no-credential resolution paths.",
          "is_bot": false,
          "headline": "fix(mcp): accept stored PAT / RUNOS_API_KEY in MCP auth gate (bug 86)",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-08T10:48:08Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "61e8769b4cb3c229dc63c5dffafc27f1a69dac7c",
          "body": null,
          "is_bot": false,
          "headline": "test(config): shorten bug-85 PAT fixture below secret-scan threshold",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-08T07:18:14Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "f8d8399ff62091d08da12ae168c5fb64263ed15c",
          "body": "…ug 85)",
          "is_bot": false,
          "headline": "docs(changelog): v1.7.4 fix stored-PAT shadowing interactive login (b…",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-08T07:16:52Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "e67b0d0cf3732b4dceabfc6bb2790883a79b1433",
          "body": "Interactive browser login and device preauth wrote a fresh\nrefresh_token but never cleared a stored api_key from a prior\n'runos login --api-key'. ResolveToken ranks a stored PAT above the\nFirebase refresh session, so the stale PAT shadowed the fresh login\nand every call 401'd with \"Invalid token\" de\n[…]\nng RefreshToken in reverse. One credential is\nlive post-login either way.\n\nRegression test pins the api_key clear; existing\nTestResolveToken_StoredAPIKeyWinsOverFirebase pins the precedence\nmechanism.",
          "is_bot": false,
          "headline": "fix(login): clear stored api_key on non-PAT login (bug 85)",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-08T07:02:35Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "00695d82b15ca9f0b6844b011194e4636c1ebc01",
          "body": null,
          "is_bot": false,
          "headline": "docs(changelog): v1.7.3 phantom patch for obj-55 installer verify",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-07T16:27:14Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "7d744ec079c1ec6c8c6021c5ade0269465950fa9",
          "body": null,
          "is_bot": false,
          "headline": "docs(changelog): v1.7.2 supervised re-rollout target (obj 52)",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-06T16:27:30Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "96fd103670f1a5f483b7f7cd9ee85ae35dea0175",
          "body": "…n-controlled\n\nRework the deploy runbook so tooling never touches main. A deploy now tags the\ndev commit, pushes the tag + dev, and on success fast-forwards a new 'deployed'\nbranch to the shipped commit. main is left for the human to merge after personal\nverification. The sensitivity scan and payloa\n[…]\nepo structure, collapsed to a single 'deployed' branch\nsince the CLI has no dev/beta/prod environments. Updates scripts/release.sh,\nthe release-cli skill, and the CLAUDE.md Releasing section to match.",
          "is_bot": false,
          "headline": "chore(release): deploy branch model (dev/deployed/main); main is huma…",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-06T16:02:31Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "4b1fdee7e5a83ea80bcf1cbc7cb059884473d3b5",
          "body": "…ine release",
          "is_bot": false,
          "headline": "docs(changelog): v1.7.1 release runbook + first live validation-pipel…",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-06T13:41:16Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "573c05814b0caf228560f563e566798e5a0f75fb",
          "body": "Add scripts/release.sh, the single deterministic path for cutting a CLI\nrelease: gates (go build/vet/test, make local), fast-forward main to dev,\ntag, push, watch CI, and verify the build-provenance attestation. Includes\na fail-closed sensitivity scan over the release payload (public repo).\n\nAdd the\n[…]\nravels with the repo while\nlocal Claude state stays ignored.\n\nWire make release VERSION=vX.Y.Z (CHECK=1 for a no-side-effect dry run) and\nrepoint the CLAUDE.md Releasing section at the skill + script.",
          "is_bot": false,
          "headline": "chore(release): deterministic release runbook (script + skill + gates)",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-06T12:53:52Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "51a2b11b916245417bb4bce802c66a889cc20461",
          "body": null,
          "is_bot": false,
          "headline": "docs(changelog): v1.7.0 build-provenance attestation + asset rename",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-06T11:12:08Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "a657e2edad6137cfcad5ba62ea462ac02c5ab332",
          "body": "…man S88)\n\nDocument the build-provenance trust chain (keyless attestation -> Templates\nvalidator + R2 digest registry -> fail-closed installers) and the accepted\nlimitation that attestation does not defend against a compromised build.\n\nEnumerate the admin-only GitHub-settings controls that are the o\n[…]\nion (branch protection, required review, restricted workflow\nedits, restricted tag/release publishing), and note that every uses: in\nrelease.yml and ci.yml is already SHA-pinned with version comments.",
          "is_bot": false,
          "headline": "docs(security): release trust model + admin hardening checklist (fore…",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-06T08:25:19Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "5ec751056f2e876b37a9437b9ef81f35e33488e7",
          "body": "…{os}-{arch}\n\nAdd keyless (Sigstore/OIDC) actions/attest-build-provenance step to the\nrelease workflow, pinned to v4.1.0 SHA, attesting every released archive\nto the workflow OIDC identity. Add id-token:write + attestations:write\npermissions.\n\nDrop the literal 'latest' from archive filenames (runos-\n[…]\nleases/latest/download/runos-latest-* URLs stop\nresolving for new releases; consumers must move to exact-tag URLs. Rolls\nout together with the Templates validator + new installer (foreman obj-50 S84).",
          "is_bot": false,
          "headline": "feat(release): build-provenance attestation + rename assets to runos-…",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-06T07:53:54Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "145e5f9cac587dc3c8a5f6f858a4ea33f99dfe08",
          "body": "…; add login --api-key\n\nReplace every real RunOS identifier and customer name in fixtures, help\ntext, comments and docs with obvious placeholders (myacct, mycluster,\nmyapp/appidN, myosid, mysvc, acme). No real aid/cid/app-id/service-id/\nosid or customer/org name remains in the tree. Pure rename; ful\n[…]\nAT > Firebase precedence); logout clears it. CHANGELOG v1.6.0.\n- delete TESTING.md and its CLAUDE.md reference; fold the testing\n  conventions into CLAUDE.md with a hard rule never to commit real ids.",
          "is_bot": false,
          "headline": "chore(security): purge real account/cluster/app/service ids from repo…",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-05T09:45:35Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "5053da85b0445082e856fbd12b7868b47e5b4f13",
          "body": "…7 S80)\n\nBuild an auxiliary (non-app) container image from a local build context\nand push it to the cluster's system Harbor under the fixed runos-apps\nproject as runos-apps/<repo>:<tag>. Decoupled from apps/deploy/VCS: no\napp id, no commit SHA, no robot/RBAC/GitHub cred, no phantom build-only\napp. S\n[…]\n membership (both option sets), upload-URL hardening, MCP\narg translation + required-arg errors, JSON envelope + summary phrasing.\nDocs: CHANGELOG v1.5.0, CLAUDE.md section, TESTING.md regression row.",
          "is_bot": false,
          "headline": "feat(cli): services harbor build-image verb + MCP shim (foreman obj-4…",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-04T16:07:21Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "52b389f47df8c9571f5520f68d1310f271a1fe73",
          "body": null,
          "is_bot": false,
          "headline": "docs(changelog): v1.4.2 --app in --help + --command literal pass-through",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-03T16:36:38Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "0737fbcdae8e6060294e84109ea8e7acc9e25b0f",
          "body": "…rals (foreman #82 + #83)\n\n#82: bug 80 added --app as a normalizer-redirect alias for --id on\napps-scoped manifest commands (apps/run, apps/show, apps/logs, ...).\nThat redirect was invisible in --help, so users had no way to discover\nthe alias.\n\nFix: register --app as a real cobra flag in dynacmd's \n[…]\neywords, numerics, JSON fragments, mixed argv all\n  survive as []string\n- TestCoerceArrayFlagValue_ObjectItemTypeStillJSONCoerces (#83):\n  object/array itemType still JSON-decodes\n\nTESTING.md updated.",
          "is_bot": false,
          "headline": "fix(cli): make --app visible in --help + stop --command coercing lite…",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-03T16:29:24Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "6b8fe79496a2c2dd247b8faa703666357cdf2dad",
          "body": "…ommands",
          "is_bot": false,
          "headline": "docs(changelog): v1.4.1 --app alias for --id on app-scoped manifest c…",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-03T09:15:13Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "0d5db91cab661109aca6444999072fed75828de4",
          "body": "… (foreman #80)\n\n`runos deploy` (hand-coded) and the new `runos apps build` (hand-coded\nin objective 43 / story 74) both accept --app <id>. Manifest-driven app\ncommands (`apps run`, `apps show`, `apps logs`, ...) only registered\n--id from the manifest's `id` positional, so `runos apps run --app\n<id>\n[…]\npid8` accepts the flag\n(falls through to \"missing sha+command\" instead of \"unknown flag\");\n`runos services postgresql show --app appid8` still errors with\n\"unknown flag: --app\".\n\nTESTING.md row added.",
          "is_bot": false,
          "headline": "fix(cli): accept --app alias for --id on app-scoped manifest commands…",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-03T09:10:50Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "afb9852baa2b86eeef5e8752c6284e05dcaf9aef",
          "body": null,
          "is_bot": false,
          "headline": "docs(changelog): v1.4.0 runos apps build verb + mcp empty-body fix",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-03T08:16:09Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "31b4dfd3587265bc71b1f9e71817fc060be75214",
          "body": "…t-in gate) (story 74)\n\nAdds the CLI side of objective 43: a standalone `runos apps build` verb\nthat drives the conductor's new POST /apps/:id/build endpoint to build +\npush a VCS app's SHA-keyed image to Harbor and stop, with no rollout\nand no command run. Optional, opt-in for clean CI logs; build-\n[…]\ntempty contract; *bool keeps\n  explicit `false` distinguishable from absent)\n- TestJobStatus_BuildResult (typed result decoding from raw JSON)\n\nTESTING.md updated with the objective-43 / story-74 row.",
          "is_bot": false,
          "headline": "feat(cli): runos apps build verb (standalone image build, --follow op…",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-03T07:40:03Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "090db67db7b4eb0980443e867775c1ce5d7e5b8c",
          "body": "…(foreman #78)\n\nContentBlock.Text had `json:\"text,omitempty\"`, so any empty-text success\nframe marshalled to `{\"type\":\"text\"}` with no `text` field. MCP rejects\nthat as an invalid_union; conformant clients see a Zod validation dump\neven though the underlying call (e.g. account/api-keys/revoke, a 200\n[…]\nreturned \"\" to the wrap layer.\n\nRegression tests pin both: TestContentBlockMarshalsTextEvenWhenEmpty\n(struct tag invariant), TestIsEmptyBody + TestEmptyBodySuccessMessage\n(executor empty-body branch).",
          "is_bot": false,
          "headline": "fix(mcp): emit valid text content block for empty-body 2xx responses …",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-03T07:06:19Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "21281a24625d03a604f4eee922acc10542d33ccf",
          "body": "…tory 70)\n\nScope change from objective 42's round-2 verification: the original\n\"leave requires.env naming as-is\" decision was reversed. requires.env\nnow accepts BOTH `username` (preferred) and `user` (back-compat alias).\nThe CLI iterates the requires.env map field-key-agnostically already,\nso this i\n[…]\nThe collision-detection / drift-gate\nhelpers iterate the map agnostically, so `username` and `user` already\nboth flow through; the pin guards against a future refactor accidentally\ndropping the alias.",
          "is_bot": false,
          "headline": "docs(cli): prefer `username` in requires.env, keep `user` as alias (s…",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-02T14:25:36Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "43be2514847b5cbd115cc1405a25e1ca61ae0858",
          "body": "The verb itself is fully manifest-driven (manifest 28.15.0, conductor\ncommit 7bac12a), so this commit is the CLI repo's slice of objective\n42: documentation and a regression test pinning the discrete-field\nrequires.env iteration.\n\n- CLAUDE.md: new \"Postgres adopt-user and discrete-field requires.env\n[…]\nalse-positive drift gate for the non-url keys on an\n  adopted-user app).\n\nNo static cobra code added, no MCP shim added: verb is auto-rendered\nby the dynacmd builder once the manifest entry is pulled.",
          "is_bot": false,
          "headline": "docs(cli): adopt-user verb + discrete-field requires.env (story 68)",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-02T14:04:06Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "8cb1b51489228c3a866c3294be72254eae8acf12",
          "body": "Adds the user-facing entry point for objective 41. Top-level `runos\nrun` sibling to `runos deploy`: deploy is build then rollout, run is\nbuild then execute. Targets pre-rollout work (DB migrations, seeds,\nbackfills) that today still requires kubectl exec and cluster admin in\nCI, blocking retirement \n[…]\ners: --timeout parsing, the VCS-only\npreflight, exit-code extraction off JobStatus.RawResult, the\nexitCodeError ExitCode() interface contract, the MCP arg builder, and\nthe static-tool category gating.",
          "is_bot": false,
          "headline": "feat(cli): runos run verb (CI + laptop shapes, VCS-only, exit-code prop)",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-02T10:06:16Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "caea43e517380aa076ceb0aea97cbc8c31500ae3",
          "body": null,
          "is_bot": false,
          "headline": "docs(changelog): v1.1.0 docker build args on runos deploy",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-01T15:31:32Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "862744217690e3ea11ccfc2b4c4f98aa2d411231",
          "body": "Implements story 60 of objective 40 on the CLI side. Adds Docker build\nargs support to `runos deploy` for both CLI-deploy and VCS-deploy paths,\nso apps needing build-time configuration (Next.js bundles baking\nNEXT_PUBLIC_* etc.) no longer need bespoke GitHub Actions workarounds.\n\n- DeployConfig gain\n[…]\nnos subprocess.\n\nPure-helper tests for the parser, validator, dup-key detector, yaml\nround-trip, wire-shape on both deploy paths, and the MCP translation.\nTESTING.md updated with the regression entry.",
          "is_bot": false,
          "headline": "feat(cli): docker build args on runos deploy (yaml + --build-arg)",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-06-01T14:48:24Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "750e95558716cdd90a5856a63e6e3b477ce1158d",
          "body": null,
          "is_bot": false,
          "headline": "docs(changelog): v1.0.0 GA notes covering rc.8 -> 1.0.0 deltas",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-31T17:09:28Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "5f4996ee32b464808547b9969908e637a41489d7",
          "body": "… deploymentStrategy round-trip\n\napps pull now writes a thin yaml on a named RRC (just resourceRequirementClassId, no replicas/cpu/memory) since the class bakes those in. Custom rrcId still emits every dimension. apps sync detects when a local override on a named RRC conflicts with the class default\n[…]\nround-trips through apps pull and reaches the deploy wire body, so a yaml setting `deploymentStrategy: recreate` (single GPU, RWO PVC) no longer silently falls back to the conductor's rolling default.",
          "is_bot": false,
          "headline": "feat(cli): thin-yaml RRC pulls, custom flip on conflicting overrides,…",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-31T17:07:47Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "3892179b9c9ad2a9792398dfbbf67e47bc64fa77",
          "body": "The generic key:value formatter rendered the {kubeconfig: \"...\"}\nresponse as `kubeconfig: <multiline YAML>`, prefixing the YAML with a\nstray top-level key. That broke the documented use case of piping the\noutput straight to kubectl (\"yaml: mapping values are not allowed in\nthis context\"). The only w\n[…]\nng one entry instead of duplicating the carve-out.\n\nRegression test on the pure helper (positive on clusters/kubeconfig,\nnegative on missing/empty/wrong-typed field, invalid JSON, and other\ncommands).",
          "is_bot": false,
          "headline": "fix(cli): emit clusters/kubeconfig raw YAML in text mode (foreman #48)",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-31T09:28:22Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "af620290812c0df552d943c9bcb2fbbf95753b48",
          "body": "…y/N] (foreman #37)\n\nConductor manifest 28.6.0 surfaces the maintenance-scripts run path\nunder `mcp:[\"write\"]` so dynacmd auto-generates the command and MCP\ntool. The new node-apt-upgrade-reboot script reboots the node, but\nthe matcher's last-segment test (`run`) is too generic to add as a\nblanket d\n[…]\nntenance-scripts trigger; unrelated `…/run` paths stay non-gated.\n\nRegression rows in TestIsDestructiveCommand cover the positive match,\na hypothetical future script, and a negative (`apps/{id}/run`).",
          "is_bot": false,
          "headline": "feat(cli): gate maintenance-scripts run triggers behind destructive […",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-30T09:28:27Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "249bec58c4d2cac8133754b0aef62a8df2924234",
          "body": "The set-*-config family declares its fields as type: string to match\nthe Record<string,string> wire contract Bug #36 enforced, but the -f\nYAML loader in collectInput copied parsed values to the body verbatim.\nA `queue_size: 128` therefore reached Conductor as a JSON number and\nwas rejected (\"expecte\n[…]\n\nstrings. Unconvertible values pass through so the server can return\nits own typed error. Generic and manifest-driven, not endpoint-specific.\n\nRegression tests on the pure helpers in executor_test.go.",
          "is_bot": false,
          "headline": "fix(cli): coerce -f YAML values per manifest field type (foreman #40)",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-30T08:17:27Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "67defb8b449c37d5b9519c5fddc232407cb09cd1",
          "body": "The server returns 200 OK with `{cancelled:false,message:\"...\"}` when\nthe target job is already terminal. The structured body was rendered\ncorrectly but the process exit code stayed 0, so CI / LLM gates keyed\non $? misread the noop as success. Add jobsCancelExitGate (mirroring\ndomainCheckExitGate) and wire it next to the existing exit-gate\ndispatch. The body still renders; only the return value flips.",
          "is_bot": false,
          "headline": "fix(cli): jobs cancel exit code reflects cancelled:false (foreman #147)",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-18T07:58:07Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "45525f15e7a1cba2a13974a4d44c4c7c03d4c65e",
          "body": "…#145)\n\nformatAuthError rewrote every 401 with a generic RUNOS_API_KEY\nremediation, including the upstream-provider 401s that flow through\nintegrations/add/<provider>. Operators chasing a bad Hetzner /\nDigitalOcean token were pointed at runos account api-keys add when\nthe real fix is to rotate the p\n[…]\nh-based\nclassifier (is401UpstreamProxyCommand) and skip the formatAuthError\nrewrite for integrations/add/*; the conductor's APIError body\n(which already carries the provider message) renders verbatim.",
          "is_bot": false,
          "headline": "fix(cli): skip auth-refused rewrite for upstream-proxy 401s (foreman …",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-18T07:50:41Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "8f14d487a4814e51b2d20f115a330f6173a5fac5",
          "body": "exec-sql is the only verb in destructiveVerbSuffixes whose actual\ndestructiveness depends on a runtime flag. Pre-fix, every `services\npostgresql exec-sql ... --query \"SELECT 1\"` required --yes, training\noperators to bypass the prompt for routine diagnostic reads. Introduce\ndestructivePromptApplies t\n[…]\n the prompt. Other destructive\nverbs (delete / drain / reset / clear-cache / ...) stay gated\nunconditionally; --yes flag registration is unchanged so users can\nstill opt out on read-write invocations.",
          "is_bot": false,
          "headline": "fix(cli): skip destructive-confirm on read-only exec-sql (foreman #140)",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-18T06:44:06Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "b8aa63911d5731cf16ee505d1d67feeda161efc3",
          "body": "A bare RFC 3339 timestamp like \"2026-08-01T00:00:00\" reached the\nconductor and was interpreted as local time, producing PATs that\nexpired up to 14 hours off the operator's intent on non-UTC machines.\nPre-flight the value with time.Parse(time.RFC3339, ...) inside the\nexisting applyCLIDefaults carve-out and refuse with a message naming\nboth accepted shapes (Z suffix or numeric offset).",
          "is_bot": false,
          "headline": "fix(cli): refuse tz-less --expires-at on api-keys add (foreman #132)",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-18T05:33:41Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "202aa26a1a52ac37f25d6c3c71365cfe5df1c747",
          "body": "…131)\n\ndestructiveSummary only consulted the positional args slice, so endpoints\nthat accept the URL-bound id via --cid (clusters/{cid}/reset) leaked the\nmanifest description in the confirmation message. Read the matching\n--<flagNameFor(field.Name)> value when the positional slot is empty, so\nthe target line consistently reads cid=mycluster3 (matching the apps delete\nid=c479n shape).",
          "is_bot": false,
          "headline": "fix(cli): destructive-confirm target surfaces --flag value (foreman #…",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-18T05:27:10Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "a08b5b3e7a7119339aac7b1f1180e3fc0b80cd59",
          "body": "Follow-up to #86/#93/#94. Two bare stdout writes in cmd/deploy.go's\n--follow success branch still landed on stdout under --json:\n  - \"Deployment completed successfully!\" (line 670)\n  - \"App available at: <url>\" (line 694)\n\nBoth now route through humanOut, the io.Writer already plumbed by the\n#94 com\n[…]\nly thing on stdout under --json, restoring\n`python json.load(out)` / `jq` parsing on completed deploys.\n\ndeploy_vcs.go's analogous block already uses the `human` writer\ncorrectly via vcsDeployStreams.",
          "is_bot": false,
          "headline": "fix(cli): deploy --follow --json success stdout leaks (foreman #126)",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-17T20:47:57Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "e69ab0fc3464f8d27292907d925d84da50235ff9",
          "body": "`runos apps prepare-cli-pull c479n bogus --cid mycluster3` returned a 400\n\"cliUploadId is required\" because the second positional was silently\ndropped. Only `--cli-upload-id <id>` or `-f body.yaml` bound the value.\nThe help text correctly showed `<id> <cliUploadId>` so the contract\nwas right; only t\n[…]\ning behaviour is preserved.\n\nLive verified: `apps prepare-cli-pull c479n bogus --cid mycluster3` now\ncorrectly sends cliUploadId=bogus to conductor (404 Archive not\nfound, as expected for a bogus id).",
          "is_bot": false,
          "headline": "fix(cli): bind body-positional args into request body (foreman #122)",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-17T16:52:33Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "dd45dc46d0e0f43e586d5736116ebb515a0bf8c7",
          "body": "…an #117)\n\nFollow-up to #104. The strict-yaml decoder error for known apps-add\nbody fields (`envVars`, `secretEnvVars`) used to bottom out at \"field\nenvVars not found in runos.yaml\" with no path forward — users had\ncopy-pasted from the apps/add manifest help into their runos.yaml.\n\nAdded envFieldHin\n[…]\ner behaviour). Non-strict-decoder yaml errors are untouched.\n\nRegression tests cover both hints, an unknown-field no-hint case, the\nnon-strict-decoder pass-through, and an end-to-end LoadConfig check.",
          "is_bot": false,
          "headline": "fix(cli): did-you-mean for envVars/secretEnvVars in runos.yaml (forem…",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-17T15:52:22Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "bb9d1f4b5aa74b0f9d1a80a22e546c090b77f080",
          "body": "A PAT pasted from Slack / docs / web UIs often carries a trailing\nnewline (Cmd-A Cmd-C from a chat client) or leading space. Pre-fix:\n  - trailing newline: leaked \"request failed: Get '...':\n    net/http: invalid header field value for Authorization\" because\n    net/http refuses CR/LF in header valu\n[…]\ning \"set but empty\" refusal instead of reaching the net/http\nlayer.\n\nRegression tests cover the trailing-newline / CRLF / leading-space /\nsurrounding-whitespace cases plus the whitespace-only refusal.",
          "is_bot": false,
          "headline": "fix(cli): trim whitespace on RUNOS_API_KEY (foreman #110)",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-17T15:01:27Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "18be81d753e0dd45e309df2de5c039b9676c7188",
          "body": "Follow-up to #102 (the 40-hex shape gate). git treats commit SHAs as\ncase-insensitive — `git rev-parse 61D950...` works — but\nvalidateCommitSHA stayed strict on lowercase. Users pasting a SHA from\nGitHub / GitLab web UIs hit \"--sha contains non-hex character 'D'\".\n\nFix: strings.ToLower the resolved \n[…]\nalidateCommitSHA itself stays strict so the\ncontract is enforced at one place.\n\nRegression test pins the round-trip: validator alone refuses\nuppercase, normalise-then-validate accepts both case forms.",
          "is_bot": false,
          "headline": "fix(cli): normalise --sha to lowercase before validating (foreman #109)",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-17T14:58:45Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "e480c78d4317e88ccaa121e4d08e2dd8073ee83d",
          "body": "`runos apps sync < /dev/null` (no TTY, no --yes) silently applied the\npatch and exited 0. CI / cron jobs running sync without an explicit\nopt-in got unconfirmed mutations. apps delete already refuses in this\nshape via the destructive-confirm gate; apps sync now matches.\n\nThe pre-fix logic (I25-AE) a\n[…]\n> prompt as before.\n\nScope limited to apps sync (the filed issue). deploy / services_sync\nstay on the auto-skip pattern; if the same footgun surfaces there,\nfile a separate issue to extend the policy.",
          "is_bot": false,
          "headline": "fix(cli): apps sync refuses non-TTY without --yes (foreman #107)",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-17T14:34:43Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "adc623f493b95ae4a9f7a9458e6070e4f14ecf87",
          "body": "… #106)\n\n`runos apps network-access <id>` truncates the LINK column to 40 chars\nregardless of terminal width:\n`https://app-c479n-3000.testing.mercat...`. Users came for the URL\nspecifically; the command is unusable in text mode without --json.\n\ntruncateCell now bypasses the maxTextCellWidth cap when\n[…]\nlues (250-char names, descriptions, uids) still\ntruncate to keep tables readable in narrow terminals.\n\nRegression tests cover http/https URL bypass plus a substring-only\ncase that must still truncate.",
          "is_bot": false,
          "headline": "fix(cli): exempt URL-shaped cells from text-table truncation (foreman…",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-17T14:31:49Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "08190b308fc67347b3bac580f007943a5707c56c",
          "body": "MCP exposed `jobs_follow` as a tool, but the CLI had no\n`runos jobs follow` subcommand. Users moving between MCP-driven\nflows and the bare CLI hit a surprise parity gap; `runos jobs show\n--follow` errored \"unknown flag: --follow\" too.\n\nAdded cmd/jobs.go with a static `jobsCmd` parent and a\n`jobsFoll\n[…]\nnder the same parent — `runos jobs --help` now lists all six.\n\nTop-level `runos follow <jobId>` stays as the alias the MCP tool\nshells to; both surfaces now expose the verb under the `jobs`\nnamespace.",
          "is_bot": false,
          "headline": "fix(cli): add `runos jobs follow` static subcommand (foreman #105)",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-17T14:29:10Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "9b42ff7e111986f1fdc841060a250f3098e47ba0",
          "body": "Two CLI-side error-message inconsistencies fixed:\n\n1. Validate's \"app name is required in runos.yaml\" misled users to try\n   `name:` (the conductor manifest's body-field name on apps add).\n   Renamed to \"`app:` field is required in runos.yaml\" so the user\n   follows the actual yaml key.\n\n2. yaml.v3'\n[…]\n, not\nthe yaml schema. The two are distinct surfaces and the manifest\nwording is correct.\n\nRegression tests cover the sanitizer (rewrite, idempotence,\npass-through) and an end-to-end LoadConfig check.",
          "is_bot": false,
          "headline": "fix(cli): runos.yaml error wording (foreman #104)",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-17T14:26:19Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "9109ddca446137b65f6f7b55160c5b2f86f66067",
          "body": "…c (foreman #103)\n\nEight apps subcommands (show, status, logs, builds, restart, delete,\nenv-vars, update) take a 5-char app id as positional. Three (pull,\ndiff, sync) take a yaml file path. Users who learned the id-positional\nshape ran `runos apps pull c479n` and hit \"yaml file\n'<cwd>/c479n' not fou\n[…]\nps pull / diff / sync.\n\nKeeps the documented yaml-file positional semantic; just makes the\nfailure mode actionable. Regression tests cover the helper (10\ninputs) and the error-wording shape (3 cases).",
          "is_bot": false,
          "headline": "fix(cli): hint --app-id on id-shaped positional in apps pull/diff/syn…",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-17T14:22:05Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "bfa611e341b1fbdc049c94f8534cc751ce06030a",
          "body": "`runos deploy --app X --sha <short>` (e.g. the 7-char form `git log\n--oneline` emits) returned a jobId and only async-failed at step 1\n\"Fetch source: fatal: couldn't find remote ref <short>\". Git requires\nthe full ref on the server side; only 40-char SHAs work.\n\nAdded validateCommitSHA pure helper t\n[…]\ns 40-hex.\n\nError names the offending value + length and points at\n`git rev-parse <ref>` as the way to expand a short ref. Regression\ntest covers 40-hex pass, short, empty, uppercase, non-hex, 41-char.",
          "is_bot": false,
          "headline": "fix(cli): refuse short --sha pre-network on VCS deploy (foreman #102)",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-17T13:37:12Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "055b4ef8c91fe51d7d09ff196d670952395985ae",
          "body": "`runos follow <jobId>` is the canonical \"block until a job ends\" verb\n(suggested by apps-sync's \"Follow rollout: runos follow <id>\" hint),\nbut it had no --json flag. CI / LLM consumers that wanted machine-\nreadable progress were stuck with human text or had to poll\n`jobs show` in a loop.\n\nAdded -j/-\n[…]\nstderr.\n\nFailure case preserved: if the job's terminal status is `failed`,\nFollowJob* returns non-nil; the JSON envelope still emits to stdout\nand the error propagates so the exit code stays non-zero.",
          "is_bot": false,
          "headline": "fix(cli): runos follow -j/--json mode (foreman #99)",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-17T09:28:34Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "2ca55f678d1628fab656ed09d2ec17ae94027744",
          "body": "After #86 (drift refusal) and #93 (--follow), the deploy success path\nstill wrote four human-text lines to stdout regardless of --json:\n  - \"Wrote .dockerignore: ...\" (writeDeployIaCArtifacts)\n  - \"Wrote env file: ...\"        (writeDeployIaCArtifacts)\n  - \"Wrote service yaml: ...\"    (writeProvision\n[…]\nPrintf inside the helpers becomes fmt.Fprintf(humanOut, ...);\nthe JSON envelope at line 590 still emits on real stdout.\n\nMirrors the I10-L `progress` helper that was already in place for the\npreamble.",
          "is_bot": false,
          "headline": "fix(cli): deploy --json success-path stdout pollution (foreman #94)",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-17T07:22:15Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "6eb72e664165f276d773d6a0ddc9ffd8a84a5c5e",
          "body": "…eman #93)\n\nAfter the #86 drift fix, `runos deploy --follow --json` still wrote the\nbuildctl progress lines (`#5 1.623 (1/39) Installing ncurses-...`) to\nstdout before the terminal JSON envelope, breaking jq parsers.\n\nThe CLI-deploy follow call used jobs.FollowJob which hard-codes stdout\nas the writ\n[…]\n to jobs.FollowJobToWriter and pass\nos.Stderr when jsonOutput is set. Also moves the \"Following job\nprogress...\" preamble through the existing `progress` helper so it lands\non stderr under --json too.",
          "is_bot": false,
          "headline": "fix(cli): deploy --follow --json routes build progress to stderr (for…",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-17T07:21:39Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "9e996d909c2c530601d297bf0b45b7367241d345",
          "body": "A runos.yaml with `cpuRequestMc: 0.5` (or any fractional integer field)\npassed silently because yaml.v3 truncates a float literal to int(0)\nwhen the target struct field is int-typed. k8s reads limit=0 as\nUNLIMITED, so the user thinks they capped resources while the pod\nactually runs uncapped.\n\nAdded\n[…]\nfore LoadConfig's typed decode so the rounding never\nhappens.\n\nRegression tests cover the issue 91 repro, memory + replicas variants,\nthe integer-passes happy path, and an end-to-end LoadConfig check.",
          "is_bot": false,
          "headline": "fix(cli): refuse fractional cpu/mem yaml literals (foreman #91)",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-16T21:09:32Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "b67d89232d798c89c60d9bc9aa7db728c2825fbc",
          "body": "A runos.yaml with a block-scalar healthCheckPath, e.g.\n\n    healthCheckPath: |\n      /healthz\n      /readiness\n\ndeployed cleanly and persisted '/healthz\\n/readiness\\n' on the\nAppDocument. k8s probe semantics don't define behaviour for paths with\nembedded newlines, so the probe silently misbehaves un\n[…]\n\n\nRegression tests cover the issue 88 repro, trailing newline, tab,\nmissing leading /, embedded whitespace, plus an end-to-end LoadConfig\ntest that confirms the block-scalar input is rejected on load.",
          "is_bot": false,
          "headline": "fix(cli): validate healthCheckPath / metricsPath shape (foreman #88)",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-16T20:59:41Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "06ec9365e9382c3efe582173d1f0703731cb6bd7",
          "body": "…#87)\n\nA .secrets.env with bytes like \\x01..\\xff..\\xfd passed CLI + conductor\nintake, exit 0 with a jobId, then failed at step 4/10 with kubectl\nemitting \"yaml: control character\". Without --follow the user saw\nsuccess while no env was actually applied.\n\nAdded envfile.Validate that walks each value \n[…]\nsync paths refuse before the request is\nqueued. Error message names the offending key + byte offset.\n\n\\n / \\r / \\t still pass for multi-line content (PEM blocks, JSON\npayloads with embedded newlines).",
          "is_bot": false,
          "headline": "fix(cli): env-value control-byte / invalid-UTF-8 pre-flight (foreman …",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-16T20:57:31Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "1c98b9e1d90cf0644215811d1ae41e9141cdbde3",
          "body": "`runos deploy --json` on drift wrote the human-readable drift report\n(boxes, --- local/+++ server, reconcile hints) to stdout before the\n{\"error\":...} JSON envelope, so `python3 json.load(out.txt)` raised\nJSONDecodeError. Pre-existing --force path already routed its warnings\nto stderr; the refusal p\n[…]\ne JSON error envelope still emits to stdout via\nthe runDeploy defer'd emitJSONError path.\n\nRegression test pipes os.Stdout and asserts the gate writes zero bytes\nto it under --json on a drift refusal.",
          "is_bot": false,
          "headline": "fix(cli): deploy --json drift refusal stdout pollution (foreman #86)",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-16T20:53:55Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "7d9b2143a27eaffcf9341574d4899b3191d94416",
          "body": "Accumulated work from earlier sessions that landed in the working tree\nbefore the foreman-driven per-issue commit workflow. Bundled here so\nthe per-issue fixes above don't carry ride-along noise. Touches:\n\n  - cmd/parents.go + parents_test.go (new): top-level `runos parents`\n    helper for the apps/\n[…]\nructive.go + destructive_test.go (new):\n    destructive-op confirmation helper\n  - internal/update/updater.go, updater_test.go: update endpoint\n    follow-ups\n\nNo new test failures; full suite passes.",
          "is_bot": false,
          "headline": "chore: carry-forward CLI changes from prior session",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-16T19:33:50Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "542ea39cb6737eeb504724574a57bc7ac758d609",
          "body": "`runos apps pull <yaml>` and `runos services pull <yaml>` refused with\n\"cluster mismatch: yaml is for X but default is Y\" when the user's\nconfigured default cluster differed from the yaml's cid — even though\nboth --help texts promise the cid is read from the file. Broke the\ndocumented round-trip for\n[…]\ng help with exit 0\n  - cmd/apps_pull.go / cmd/services_pull.go SilenceUsage + ENOENT\n    wording polish\n\nRegression tests cover both reconciliation branches and the existing\nexplicit-mismatch refusal.",
          "is_bot": false,
          "headline": "fix(cli): pull honors yaml cid over default (foreman #83)",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-16T19:33:31Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "a28693f9a70b8f0d93e33606bb35e59e0de676df",
          "body": "Every `runos deploy` printed \"Warning: domain-removal gate skipped\n(fetch failed: ...)\" because GetAccountDomains hard-coded the legacy\nbare-array shape, but conductor's `/:aid/domains` migrated to the\nenvelope `{domains:[...]}` in the iter-27 envelope sweep. The\ndomain-removal confirmation was sile\n[…]\nts both shapes\n(bare array AND single-key envelope) so the gate fires through the\nconductor migration window and beyond.\n\nRegression test covers envelope, bare-array, empty, and malformed-input\ncases.",
          "is_bot": false,
          "headline": "fix(cli): deploy domain-removal envelope (foreman #70)",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-16T19:33:19Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "83dac664502cf86e6e38512b29298776865c230e",
          "body": "Two related fixes that share internal/deploy/config.go; bundling so the\nshared file changes stay coherent.\n\n#50 LoadConfig: switched from yaml.Unmarshal to yaml.NewDecoder +\nKnownFields(true). Typo'd top-level runos.yaml keys (replica vs\nreplicas, healtCheck, envVars, ...) now fail with a typed erro\n[…]\ness codec.\n\nRegression tests cover typo refusal, pulled-yaml round-trip,\nPEM-block / JSON / unicode values, both quote styles, leading/trailing\nwhitespace, empty values, and the on-disk format change.",
          "is_bot": false,
          "headline": "fix(cli): deploy yaml strict + lossless env-file (foreman #50, #73)",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-16T19:33:11Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "b72198f772017bd89f6c4fabccbdff307816b964",
          "body": "Five related pre-network gates in the dynacmd executor that turn\nconductor 5xx / silent-drop classes into clean local refusals. Deeply\ninterleaved in the same file, bundled here.\n\n#47 validateClusterIDShape: refuses `--cid` with slash, control char,\nor runaway length (>64 chars) before the request h\n[…]\nlicit empty values pass through to the\nmissing-required gate.\n\nRegression tests for each helper cover positional + flag/body slots,\nboundary conditions, and non-PATCH skip / non-enum field skip paths.",
          "is_bot": false,
          "headline": "fix(cli): dynacmd pre-flight defenses (foreman #47, #48, #53, #60, #69)",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-16T19:32:58Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "9f1e067c9f44325f58d258b74f2fad35c36ba9f6",
          "body": "`runos manifest list <no-match> --json` emitted `null` because the\nfiltered slice was declared as a nil `var paths []string`. jq's `.[]`\nand `.length` error on null but work on [], so downstream CI/LLM\nconsumers broke on the empty-match shape mismatch.\n\nExtracted filterManifestCommandPaths which seeds a non-nil `[]string{}`\nso the --json branch always returns an array regardless of how many\ncommands match.\n\nRegression test covers the helper and the JSON marshal contract.",
          "is_bot": false,
          "headline": "fix(cli): manifest list --json returns [] not null (foreman #39)",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-16T19:32:43Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "124c09f7a334cce54ad318672eeb7ec6c2fbe8d4",
          "body": "`runos services postgresql users <id>` printed minified raw JSON to the\nterminal instead of the tabular USERNAME/DATABASES table its peers\nrender. The response is a two-key envelope `{users:[...],\norphanSecretsDetected:[]}`; unwrapArrayEnvelope only handles single-key\nenvelopes, so formatArray's []m\n[…]\nared. --json mode is untouched.\n\nRegression test covers the postgresql users payload, multi-key\ndisambiguation, empty arrays, missing-fields-hint, bare-array pass-\nthrough, and dotted manifest fields.",
          "is_bot": false,
          "headline": "fix(cli): output multi-key envelope unwrap (foreman #38)",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-16T19:32:36Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "789243caf88823c0af119af2b405b87472f22378",
          "body": "`runos clusters default <cid>` stored any string verbatim — no whitespace\ntrim, no charset check, no existence check vs the account's clusters list.\nA typo'd or whitespace-padded id silently broke every subsequent command.\n\nNow: trim whitespace, run apps.ValidateIdentifier on the trimmed value,\nand \n[…]\nard-reject unknown ids.\n\nRegression tests cover trim, shape refusal (incl. path-traversal), the\ntwo clusters-list payload shapes (bare array + single-key envelope), and\nthe cid/id field-name fallback.",
          "is_bot": false,
          "headline": "fix(cli): clusters default trim+validate (foreman #36)",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-16T19:32:26Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "2da55f7f4d7bd1f2efdaf44dbed1ae5bec9fd603",
          "body": "`deploy.GetAppDependencies` decoded /apps/:id/dependencies directly\ninto []deploy.AppDependency. Conductor 17.7.0's envelope-everywhere\nmigration (sibling of I27-AA / I27-T sweep) wrapped the endpoint in\n{dependencies: [...]}, causing `cannot unmarshal object into Go value\nof type []deploy.AppDepend\n[…]\nn test TestGetAppDependencies_AcceptsEnvelope covers four\ncases: envelope-with-rows / legacy-bare-with-row / envelope-empty /\nlegacy-bare-empty.\n\nSame fix pattern as I26-O (envVars envelope handling).",
          "is_bot": false,
          "headline": "rc.8: I27-AG (CLI dependencies-parse envelope)",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-14T17:33:58Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "45983f502b6db445eac5c576ded5d72de8a9c209",
          "body": "Adds internal/deploy/wire_shape_i27y_test.go which captures the exact\nbytes the CLI POSTs to /prepare-cli-deployment for a monorepo-shaped\nDeployConfig (sourceDir=../../.., dockerfile=apps/api/Dockerfile)\nand asserts:\n- `dockerfile` lands on the wire as the FULL path \"apps/api/Dockerfile\"\n  (not bas\n[…]\nion wrapper). No\nfurther CLI work would change what the build server sees.\n\nThe CLI's available I27-Y surface (ResolveDockerfilePath pre-flight gate\n+ NginxDockerfileHint advisory) is already shipped.",
          "is_bot": false,
          "headline": "rc.8: I27-Y wire-shape proof + closure note",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-14T15:51:04Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "df2b31f7293aabc0928f983b3d5684ccd2ccd019",
          "body": "- I27-M / I27-N: configPath round-trips in pulled VCS yaml.\n  internal/apps/pull.go: new PulledApp.ConfigPath field with\n  yaml `omitempty`; BuildPulledApp reads raw[configPath]. With\n  sourceDir + dockerfile already surfacing via the conductor 17.7.0\n  AppDocument reconciliation, the third build-me\n[…]\nxDockerfileHint\n  helper. Non-blocking. Regression test covers bare-nginx,\n  unprivileged-drop-in (negative), multi-stage (intermediate\n  triggers), and token-boundary (`mynginxfork` doesn't trigger).",
          "is_bot": false,
          "headline": "rc.8: iter-27 R6 carry-forward CLI fixes (M/N + AE residual + G)",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-14T14:09:03Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "e89749085c45f38bea0b976e39f5e2ceb55e6b28",
          "body": "Direct probe of the archive walker against iter27-sandbox proved\nsub-bug (a) is not a CLI bug — the walker traverses sourceDir\ncorrectly and includes every source file. The 4KB archive size was\njust the actual compressed size of the small test monorepo.\n\nSub-bug (b) — buildctl --opt filename=Dockerf\n[…]\n UploadTarball burn the round-trip.\n\nRegression test TestResolveDockerfilePath covers default +\nmonorepo apps/api/Dockerfile + missing + absolute + escapes +\nDockerfile.prod + directory-shaped target.",
          "is_bot": false,
          "headline": "rc.8: I27-Y re-triage + dockerfile path pre-flight",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-14T13:28:24Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "309b4fadf7acb85a2b7f7981269d79304b8e9a3a",
          "body": "The bare-500 the test bench attributed to conductor on `apps env-vars\nset -f /dev/stdin` was actually a CLI stdin-double-drain. The\nmissing-required pre-flight calls bodyFileProvidesField (which calls\nloadYAMLFile internally) before collectInput does; the stdin cache\nwas keyed on the literal `-` sen\n[…]\ne route returns a structured 400 + Content-Type\nhint instead of falling over.\n\nRegression tests:\n- stdin alias /dev/stdin caches like dash sentinel (I27-S/X)\n- isStdinPath enumerates every stdin alias",
          "is_bot": false,
          "headline": "rc.8: iter-27-R3 fold-in (I27-S/X stdin-cache root cause)",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-14T10:14:11Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "01b8238d6440fe5f81a117f4ce362624b522e8a8",
          "body": "… hint)\n\n- I27-Z internal/deploy/service.go: DeployVCS returns &APIError on 4xx\n  (was plain fmt.Errorf), so cmd/apps_pull.go:emitJSONError's existing\n  errors.As fallback flattens the conductor body into the outer envelope\n  instead of double-encoding it as a quoted string.\n- I27-AB internal/dynacm\n[…]\nthe equals-form.\n- I27-AA verification: new sub-test pins dependents / dependencies\n  envelope keys through the shape-keyed unwrapArrayEnvelope helper.\n\nRegression tests for all three CLI fixes added.",
          "is_bot": false,
          "headline": "rc.8: iter-27-R2 fold-in (deploy --json purity + --tail 0 + bool flag…",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-14T09:14:30Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "ce9a74bf311ed4a946d979a25aeb85df26582929",
          "body": "…urity + docs)\n\n- I27-H cmd/deploy_vcs.go: replace alarmist `configPath: <not sent> — using\n  whatever the AppDocument has stored` fallback with calm\n  `configPath: (using AppDocument default)`. Test updated.\n- I27-L internal/mcp/services.go: buildServicesPullArgs maps schema\n  `service_id` to CLI `\n[…]\not committed):\n  \"Pulled yaml is NOT a full round-trip\" + \"Static-site Dockerfile\n  templates (non-root constraint)\" subsections.\n\nI27-Q (apps --help duplication) not reproduced against current build.",
          "is_bot": false,
          "headline": "rc.8: iter-27 fold-in (deploy phrasing + MCP services_pull + --json p…",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-13T20:32:24Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "f5a7417533afa196ae77370402c461100a01215a",
          "body": "- I26-N: MCP tool schemas for map-of-object fields like `requires`\n  used to project `additionalProperties: {type: \"string\"}`, forcing\n  LLM clients to stringify their payload and then hit conductor's\n  `requires.X must be an object` refusal. Manifest.Field extended\n  with ValueType + ValueFields (p\n[…]\nst description appends \"This field has no `--requires`\n  flag; pass via `-f body.yaml`\". Both visible in a single --help.\n  Same closure pattern as I26-S.\n\nCHANGELOG + TEST_LOG.md stamped per finding.",
          "is_bot": false,
          "headline": "rc.8: iter-26-R4 fold-in (I26-N MCP requires schema + I26-P closure)",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-13T19:03:42Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "542614c80b43ca72418f669ee3ef0e16a854698c",
          "body": "iter-26-R1 retest exposed two remaining CLI/conductor mismatches\nunder conductor's envelope-everywhere migration:\n\n- I26-O extended: my earlier env-vars fix only covered\n  apps_secret-env-vars + apps_env-vars. R1 found apps_list,\n  jobs_list, users, overrides, logs, network-access also moved to\n  en\n[…]\nss.\n\n5 new tests:\n  TestUnmarshalListResponse, TestUnwrapArrayEnvelope (output),\n  TestService_ListApps_AcceptsEnvelope, TestRefuseAmbiguousKeyValueArray.\n\nCHANGELOG + TEST_LOG.md stamped per finding.",
          "is_bot": false,
          "headline": "rc.8: iter-26-R1 fold-in (envelope-everywhere + k=v→JSON pointer)",
          "author_name": "Didier Breedt",
          "author_login": "didierbreedt-ros",
          "committed_at": "2026-05-13T17:13:10Z",
          "body_truncated": true,
          "is_coding_agent": false
        }
      ],
      "releases_count": 31,
      "commits_last_year": 154,
      "latest_release_at": "2026-07-16T18:50:56Z",
      "latest_release_tag": "v1.12.2",
      "releases_from_tags": false,
      "days_since_last_push": 11,
      "active_weeks_last_year": 14,
      "days_since_latest_release": 11,
      "mean_days_between_releases": 1.8
    },
    "community": {
      "has_readme": true,
      "has_license": true,
      "has_description": true,
      "has_contributing": false,
      "health_percentage": 50,
      "has_issue_template": false,
      "has_code_of_conduct": false,
      "has_pull_request_template": false
    },
    "ecosystem": {
      "packages": [
        {
          "name": "github.com/runos-official/cli",
          "exists": true,
          "license": null,
          "keywords": [],
          "ecosystem": "go",
          "matches_repo": true,
          "registry_url": "https://pkg.go.dev/github.com/runos-official/cli",
          "is_deprecated": false,
          "latest_version": "v1.12.2",
          "repository_url": "https://github.com/runos-official/cli",
          "versions_count": 48,
          "total_downloads": null,
          "dependents_count": null,
          "deprecation_note": null,
          "maintainers_count": null,
          "monthly_downloads": null,
          "first_published_at": null,
          "latest_published_at": "2026-07-16T18:40:48Z",
          "latest_version_yanked": null,
          "days_since_latest_publish": 11
        }
      ]
    },
    "popularity": {
      "forks": 0,
      "stars": 1,
      "watchers": 1,
      "fork_history": {
        "days": [],
        "complete": true,
        "collected": 0,
        "total_forks": 0
      },
      "star_history": null,
      "open_issues_and_prs": 0
    },
    "ai_readiness": {
      "has_nix": false,
      "example_dirs": [],
      "has_llms_txt": false,
      "has_dockerfile": false,
      "has_mcp_signal": false,
      "bootstrap_files": [
        "Makefile"
      ],
      "api_schema_files": [],
      "has_devcontainer": false,
      "typecheck_configs": [],
      "toolchain_manifests": [
        "go.mod"
      ],
      "largest_source_bytes": 95672,
      "source_files_sampled": 133,
      "oversized_source_files": 9,
      "agent_instruction_files": [],
      "agent_instruction_max_bytes": null
    },
    "dependencies": {
      "manifests": [
        "go.mod"
      ],
      "advisories": {
        "error": null,
        "scope": null,
        "source": null,
        "findings": [],
        "collected": false,
        "malicious": [],
        "truncated": false,
        "by_severity": {},
        "advisory_count": 0,
        "affected_count": 0,
        "assessed_count": 0,
        "malicious_count": 0,
        "assessed_package": null,
        "unassessed_count": 0,
        "direct_affected_count": 0
      },
      "ecosystems": [
        "go"
      ],
      "dependencies": [
        {
          "name": "github.com/pmezard/go-difflib",
          "manifest": "go.mod",
          "ecosystem": "go",
          "version_constraint": "v1.0.0"
        },
        {
          "name": "github.com/spf13/cobra",
          "manifest": "go.mod",
          "ecosystem": "go",
          "version_constraint": "v1.10.1"
        },
        {
          "name": "github.com/spf13/pflag",
          "manifest": "go.mod",
          "ecosystem": "go",
          "version_constraint": "v1.0.9"
        },
        {
          "name": "golang.org/x/term",
          "manifest": "go.mod",
          "ecosystem": "go",
          "version_constraint": "v0.41.0"
        },
        {
          "name": "gopkg.in/yaml.v3",
          "manifest": "go.mod",
          "ecosystem": "go",
          "version_constraint": "v3.0.1"
        }
      ],
      "all_dependencies": {
        "error": "GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository",
        "source": null,
        "packages": [],
        "collected": false,
        "truncated": false,
        "total_count": null,
        "direct_count": null,
        "indirect_count": null
      }
    },
    "maintainership": {
      "issues": {
        "open_prs": 0,
        "merged_prs": 0,
        "open_issues": 0,
        "closed_ratio": null,
        "closed_issues": 0,
        "closed_unmerged_prs": 0
      },
      "bus_factor": 1,
      "bot_contributors": 0,
      "top_contributors": [
        {
          "type": "User",
          "login": "didierbreedt-ros",
          "commits": 154,
          "avatar_url": "https://avatars.githubusercontent.com/u/243085795?v=4"
        }
      ],
      "contributors_sampled": 1,
      "top_contributor_share": 1
    },
    "quality_signals": {
      "has_ci": true,
      "has_tests": true,
      "ci_workflows": [
        "ci.yml",
        "release.yml"
      ],
      "has_docs_dir": false,
      "linter_configs": [],
      "has_editorconfig": false,
      "has_linter_config": false,
      "has_precommit_config": false
    },
    "security_signals": {
      "lockfiles": [
        "go.sum"
      ],
      "scorecard": {
        "checks": [
          {
            "name": "Binary-Artifacts",
            "score": 10,
            "reason": "no binaries found in the repo",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#binary-artifacts"
          },
          {
            "name": "Branch-Protection",
            "score": 0,
            "reason": "branch protection not enabled on development/release branches",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#branch-protection"
          },
          {
            "name": "CI-Tests",
            "score": null,
            "reason": "no pull request found",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#ci-tests"
          },
          {
            "name": "CII-Best-Practices",
            "score": 0,
            "reason": "no effort to earn an OpenSSF best practices badge detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#cii-best-practices"
          },
          {
            "name": "Code-Review",
            "score": 0,
            "reason": "Found 0/30 approved changesets -- score normalized to 0",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#code-review"
          },
          {
            "name": "Contributors",
            "score": 0,
            "reason": "project has 0 contributing companies or organizations -- score normalized to 0",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#contributors"
          },
          {
            "name": "Dangerous-Workflow",
            "score": 10,
            "reason": "no dangerous workflow patterns detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dangerous-workflow"
          },
          {
            "name": "Dependency-Update-Tool",
            "score": 0,
            "reason": "no update tool detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dependency-update-tool"
          },
          {
            "name": "Fuzzing",
            "score": 0,
            "reason": "project is not fuzzed",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#fuzzing"
          },
          {
            "name": "License",
            "score": 9,
            "reason": "license file detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#license"
          },
          {
            "name": "Maintained",
            "score": 10,
            "reason": "30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#maintained"
          },
          {
            "name": "Packaging",
            "score": null,
            "reason": "packaging workflow not detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#packaging"
          },
          {
            "name": "Pinned-Dependencies",
            "score": 10,
            "reason": "all dependencies are pinned",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#pinned-dependencies"
          },
          {
            "name": "SAST",
            "score": 0,
            "reason": "no SAST tool detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#sast"
          },
          {
            "name": "Security-Policy",
            "score": 10,
            "reason": "security policy file detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#security-policy"
          },
          {
            "name": "Signed-Releases",
            "score": 0,
            "reason": "Project has not signed or included provenance with any releases.",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#signed-releases"
          },
          {
            "name": "Token-Permissions",
            "score": 0,
            "reason": "detected GitHub workflow tokens with excessive permissions",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#token-permissions"
          },
          {
            "name": "Vulnerabilities",
            "score": 9,
            "reason": "1 existing vulnerabilities detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#vulnerabilities"
          }
        ],
        "commit": "8481c009241283058aa3a22aa1da703995385ce2",
        "ran_at": "2026-07-28T06:42:27Z",
        "aggregate_score": 4.5,
        "scorecard_version": "v5.5.0"
      },
      "has_codeql_workflow": false,
      "has_security_policy": true,
      "has_dependabot_config": false
    },
    "contribution_flow": {
      "collected": true,
      "ci_last_run_at": "2026-07-16T19:09:31Z",
      "oldest_open_prs": [],
      "last_merged_pr_at": null,
      "ci_last_conclusion": "SUCCESS",
      "oldest_open_issues": []
    }
  },
  "config": {
    "disabled_metrics": [],
    "disabled_categories": [],
    "disabled_components": {}
  },
  "source": {
    "url": "https://github.com/runos-official/cli",
    "host": "github.com",
    "name": "cli",
    "owner": "runos-official"
  },
  "metrics": {
    "overall": {
      "key": "overall",
      "band": "at_risk",
      "name": "Overall health",
      "note": null,
      "notes": [],
      "value": 49,
      "inputs": {
        "security": 45,
        "vitality": 76,
        "community": 21,
        "governance": 33,
        "engineering": 66
      },
      "components": []
    },
    "categories": [
      {
        "key": "vitality",
        "band": "good",
        "name": "Vitality",
        "value": 76,
        "weight": 0.22,
        "metrics": [
          {
            "key": "development_activity",
            "band": "moderate",
            "name": "Development activity",
            "note": null,
            "notes": [],
            "value": 66,
            "inputs": {
              "commits_last_year": 154,
              "human_commit_share": 1,
              "days_since_last_push": 11,
              "active_weeks_last_year": 14
            },
            "components": [
              {
                "key": "push_recency",
                "name": "Push recency",
                "detail": "last push 11 days ago",
                "points": 28.8,
                "status": "partial",
                "details": [
                  {
                    "code": "push_recency",
                    "params": {
                      "days": 11
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "commit_cadence",
                "name": "Commit cadence",
                "detail": "14/52 weeks with commits",
                "points": 9.7,
                "status": "partial",
                "details": [
                  {
                    "code": "commit_cadence_weeks",
                    "params": {
                      "weeks": 14
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "commit_volume",
                "name": "Commit volume",
                "detail": "154 commits in the last year",
                "points": 18,
                "status": "met",
                "details": [
                  {
                    "code": "commits_last_year",
                    "params": {
                      "count": 154
                    }
                  }
                ],
                "max_points": 18
              },
              {
                "key": "openssf_scorecard_maintained",
                "name": "OpenSSF Scorecard: Maintained",
                "detail": "30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10",
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "release_discipline",
            "band": "excellent",
            "name": "Release discipline",
            "note": null,
            "notes": [],
            "value": 90,
            "inputs": {
              "releases_count": 31,
              "latest_release_tag": "v1.12.2",
              "releases_from_tags": false,
              "days_since_latest_release": 11,
              "mean_days_between_releases": 1.8
            },
            "components": [
              {
                "key": "ships_releases",
                "name": "Ships releases",
                "detail": "31 releases published",
                "points": 27,
                "status": "met",
                "details": [
                  {
                    "code": "releases_published",
                    "params": {
                      "count": 31
                    }
                  }
                ],
                "max_points": 27
              },
              {
                "key": "release_recency",
                "name": "Release recency",
                "detail": "latest release 11 days ago",
                "points": 36,
                "status": "met",
                "details": [
                  {
                    "code": "release_recency",
                    "params": {
                      "days": 11
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "release_cadence",
                "name": "Release cadence",
                "detail": "a release every ~1.8 days",
                "points": 27,
                "status": "met",
                "details": [
                  {
                    "code": "release_cadence",
                    "params": {
                      "gap": 1.8
                    }
                  }
                ],
                "max_points": 27
              },
              {
                "key": "openssf_scorecard_signed_releases",
                "name": "OpenSSF Scorecard: Signed-Releases",
                "detail": "Project has not signed or included provenance with any releases.",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "abandonment",
            "band": "excellent",
            "name": "Abandonment",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "cap": null,
              "state": "maintained",
              "guards": [],
              "signals": [],
              "red_flag": false,
              "multiplier_pct": 100,
              "declared_reason": null,
              "unverified_reason": null,
              "unanswered_open_prs": null,
              "unanswered_open_issues": null,
              "days_since_last_merged_pr": null,
              "days_since_last_human_commit": 11,
              "days_since_last_human_commit_is_floor": false
            },
            "components": [
              {
                "key": "project_is_still_maintained",
                "name": "Project is still maintained",
                "detail": "last human commit 11 days ago",
                "points": 100,
                "status": "met",
                "details": [
                  {
                    "code": "abandonment_maintained",
                    "params": {
                      "days": 11
                    }
                  }
                ],
                "max_points": 100
              }
            ]
          }
        ],
        "description": "Is the project alive — is code being written and are releases shipping?"
      },
      {
        "key": "community",
        "band": "critical",
        "name": "Community & Adoption",
        "value": 21,
        "weight": 0.18,
        "metrics": [
          {
            "key": "popularity",
            "band": "critical",
            "name": "Popularity & adoption",
            "note": null,
            "notes": [],
            "value": 1,
            "inputs": {
              "forks": 0,
              "stars": 1,
              "watchers": 1,
              "growth_state": "unverified",
              "growth_factor_pct": 100,
              "growth_unverified_reason": "no_history"
            },
            "components": [
              {
                "key": "stars",
                "name": "Stars",
                "detail": "1 stars",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "stars",
                    "params": {
                      "count": 1
                    }
                  }
                ],
                "max_points": 60
              },
              {
                "key": "forks",
                "name": "Forks",
                "detail": "0 forks",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "forks",
                    "params": {
                      "count": 0
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "watchers",
                "name": "Watchers",
                "detail": "1 watchers",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "watchers",
                    "params": {
                      "count": 1
                    }
                  }
                ],
                "max_points": 15
              }
            ]
          },
          {
            "key": "community_health",
            "band": "at_risk",
            "name": "Community health",
            "note": null,
            "notes": [],
            "value": 44,
            "inputs": {
              "has_readme": true,
              "has_license": true,
              "has_contributing": false,
              "has_issue_template": false,
              "has_code_of_conduct": false,
              "has_pull_request_template": false
            },
            "components": [
              {
                "key": "readme",
                "name": "README",
                "detail": null,
                "points": 22.5,
                "status": "met",
                "details": [],
                "max_points": 22.5
              },
              {
                "key": "license",
                "name": "License",
                "detail": "license file present, not a recognized license",
                "points": 16.9,
                "status": "partial",
                "details": [
                  {
                    "code": "license_custom",
                    "params": {}
                  }
                ],
                "max_points": 22.5
              },
              {
                "key": "contributing_guide",
                "name": "CONTRIBUTING guide",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 18
              },
              {
                "key": "code_of_conduct",
                "name": "Code of conduct",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 13.5
              },
              {
                "key": "issue_template",
                "name": "Issue template",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.2
              },
              {
                "key": "pr_template",
                "name": "PR template",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 6.3
              }
            ]
          }
        ],
        "description": "Does the project have users, downloads, attention, and a welcoming setup for contributors?"
      },
      {
        "key": "governance",
        "band": "at_risk",
        "name": "Sustainability & Governance",
        "value": 33,
        "weight": 0.24,
        "metrics": [
          {
            "key": "maintainer_resilience",
            "band": "critical",
            "name": "Maintainer resilience (bus factor)",
            "note": null,
            "notes": [],
            "value": 10,
            "inputs": {
              "bus_factor": 1,
              "contributors_sampled": 1,
              "top_contributor_share": 1
            },
            "components": [
              {
                "key": "bus_factor",
                "name": "Bus factor",
                "detail": "1 contributor(s) cover half of all commits",
                "points": 9,
                "status": "partial",
                "details": [
                  {
                    "code": "bus_factor",
                    "params": {
                      "count": 1
                    }
                  }
                ],
                "max_points": 54
              },
              {
                "key": "commit_distribution",
                "name": "Commit distribution",
                "detail": "top contributor authored 100% of commits",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "top_contributor_share",
                    "params": {
                      "share": 100
                    }
                  }
                ],
                "max_points": 22.5
              },
              {
                "key": "contributor_breadth",
                "name": "Contributor breadth",
                "detail": "1 contributors",
                "points": 1.4,
                "status": "partial",
                "details": [
                  {
                    "code": "contributors_sampled",
                    "params": {
                      "count": 1
                    }
                  }
                ],
                "max_points": 13.5
              },
              {
                "key": "openssf_scorecard_contributors",
                "name": "OpenSSF Scorecard: Contributors",
                "detail": "project has 0 contributing companies or organizations -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "responsiveness",
            "band": "critical",
            "name": "Issue & PR responsiveness",
            "note": "Excluded from scoring (no data or not applicable): Issue resolution, PR acceptance. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "issue_resolution",
                    "pr_acceptance"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 1,
            "inputs": {
              "merged_prs": 0,
              "open_issues": 0,
              "closed_issues": 0,
              "issue_closed_ratio": null,
              "closed_unmerged_prs": 0
            },
            "components": [
              {
                "key": "issue_resolution",
                "name": "Issue resolution",
                "detail": "no issues or no data",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "no_issues_or_data",
                    "params": {}
                  }
                ],
                "max_points": 46.75
              },
              {
                "key": "pr_acceptance",
                "name": "PR acceptance",
                "detail": "no decided pull requests or no data",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "no_decided_prs_or_data",
                    "params": {}
                  }
                ],
                "max_points": 38.25
              },
              {
                "key": "openssf_scorecard_code_review",
                "name": "OpenSSF Scorecard: Code-Review",
                "detail": "Found 0/30 approved changesets -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 15
              }
            ]
          },
          {
            "key": "stewardship",
            "band": "at_risk",
            "name": "Ownership & stewardship",
            "note": null,
            "notes": [],
            "value": 40,
            "inputs": {
              "followers": 0,
              "owner_type": "Organization",
              "is_verified": null,
              "owner_login": "runos-official",
              "public_repos": 9,
              "account_age_days": 418
            },
            "components": [
              {
                "key": "ownership_backing",
                "name": "Ownership backing",
                "detail": "organization-owned",
                "points": 30,
                "status": "met",
                "details": [
                  {
                    "code": "owner_organization",
                    "params": {}
                  }
                ],
                "max_points": 30
              },
              {
                "key": "verified_domain",
                "name": "Verified domain",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 20
              },
              {
                "key": "owner_reach",
                "name": "Owner reach",
                "detail": "0 followers of runos-official",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "owner_followers",
                    "params": {
                      "count": 0,
                      "login": "runos-official"
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "track_record",
                "name": "Track record",
                "detail": "9 public repos, account ~1 yr old",
                "points": 9.6,
                "status": "partial",
                "details": [
                  {
                    "code": "public_repos",
                    "params": {
                      "count": 9
                    }
                  },
                  {
                    "code": "account_age_years",
                    "params": {
                      "years": 1
                    }
                  }
                ],
                "max_points": 25
              }
            ]
          },
          {
            "key": "package_maintenance",
            "band": "excellent",
            "name": "Package maintenance",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "packages": [
                "github.com/runos-official/cli"
              ],
              "ecosystems": "go",
              "any_deprecated": false,
              "min_days_since_publish": 11
            },
            "components": [
              {
                "key": "published_resolvable",
                "name": "Published & resolvable",
                "detail": "1 package(s) on go",
                "points": 25,
                "status": "met",
                "details": [
                  {
                    "code": "packages_published",
                    "params": {
                      "count": 1,
                      "ecosystems": "go"
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "publish_recency",
                "name": "Publish recency",
                "detail": "latest publish 11 days ago",
                "points": 35,
                "status": "met",
                "details": [
                  {
                    "code": "publish_recency",
                    "params": {
                      "days": 11
                    }
                  }
                ],
                "max_points": 35
              },
              {
                "key": "version_history",
                "name": "Version history",
                "detail": "48 published versions",
                "points": 20,
                "status": "met",
                "details": [
                  {
                    "code": "published_versions",
                    "params": {
                      "count": 48
                    }
                  }
                ],
                "max_points": 20
              },
              {
                "key": "not_deprecated",
                "name": "Not deprecated",
                "detail": "active, not deprecated or yanked",
                "points": 20,
                "status": "met",
                "details": [
                  {
                    "code": "package_not_deprecated",
                    "params": {}
                  }
                ],
                "max_points": 20
              }
            ]
          }
        ],
        "description": "Will the project survive its people — bus factor, responsiveness, who backs it, and package upkeep?"
      },
      {
        "key": "engineering",
        "band": "moderate",
        "name": "Engineering Quality",
        "value": 66,
        "weight": 0.2,
        "metrics": [
          {
            "key": "engineering_practices",
            "band": "moderate",
            "name": "Engineering practices",
            "note": "Excluded from scoring (no data or not applicable): OpenSSF Scorecard: CI-Tests. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "openssf_scorecard_ci_tests"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 60,
            "inputs": {
              "has_ci": true,
              "has_tests": true,
              "has_editorconfig": false,
              "has_linter_config": false,
              "has_precommit_config": false
            },
            "components": [
              {
                "key": "ci_workflows",
                "name": "CI workflows",
                "detail": "2 workflow(s)",
                "points": 24,
                "status": "met",
                "details": [
                  {
                    "code": "ci_workflows",
                    "params": {
                      "count": 2
                    }
                  }
                ],
                "max_points": 24
              },
              {
                "key": "tests_present",
                "name": "Tests present",
                "detail": null,
                "points": 24,
                "status": "met",
                "details": [],
                "max_points": 24
              },
              {
                "key": "linter_config",
                "name": "Linter config",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 16
              },
              {
                "key": "pre_commit_hooks",
                "name": "Pre-commit hooks",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 9.6
              },
              {
                "key": "editorconfig",
                "name": ".editorconfig",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 6.4
              },
              {
                "key": "openssf_scorecard_ci_tests",
                "name": "OpenSSF Scorecard: CI-Tests",
                "detail": "no pull request found",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "no_data",
                    "params": {}
                  }
                ],
                "max_points": 20
              }
            ]
          },
          {
            "key": "documentation",
            "band": "good",
            "name": "Documentation",
            "note": null,
            "notes": [],
            "value": 75,
            "inputs": {
              "topics": [
                "cli",
                "kubernetes",
                "runos",
                "source-available"
              ],
              "has_wiki": true,
              "homepage": "https://runos.com",
              "has_readme": true,
              "has_docs_dir": false,
              "has_description": true
            },
            "components": [
              {
                "key": "readme",
                "name": "README",
                "detail": null,
                "points": 30,
                "status": "met",
                "details": [],
                "max_points": 30
              },
              {
                "key": "documentation_directory",
                "name": "Documentation directory",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 25
              },
              {
                "key": "documentation_homepage_site",
                "name": "Documentation / homepage site",
                "detail": "https://runos.com",
                "points": 15,
                "status": "met",
                "details": [],
                "max_points": 15
              },
              {
                "key": "repository_description",
                "name": "Repository description",
                "detail": null,
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              },
              {
                "key": "topics",
                "name": "Topics",
                "detail": "4 topics",
                "points": 10,
                "status": "met",
                "details": [
                  {
                    "code": "topics_count",
                    "params": {
                      "count": 4
                    }
                  }
                ],
                "max_points": 10
              },
              {
                "key": "wiki",
                "name": "Wiki",
                "detail": null,
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              }
            ]
          }
        ],
        "description": "Are baseline engineering and documentation practices in place?"
      },
      {
        "key": "security",
        "band": "at_risk",
        "name": "Security",
        "value": 45,
        "weight": 0.16,
        "metrics": [
          {
            "key": "security_posture",
            "band": "at_risk",
            "name": "Security posture",
            "note": "Excluded from scoring (no data or not applicable): CI-Tests, Packaging. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "ci_tests",
                    "packaging"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 45,
            "inputs": {
              "source": "openssf_scorecard",
              "checks_evaluated": 16,
              "scorecard_version": "v5.5.0",
              "checks_inconclusive": 2,
              "scorecard_aggregate": 4.5
            },
            "components": [
              {
                "key": "binary_artifacts",
                "name": "Binary-Artifacts",
                "detail": "no binaries found in the repo",
                "points": 7.5,
                "status": "met",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "branch_protection",
                "name": "Branch-Protection",
                "detail": "branch protection not enabled on development/release branches",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "ci_tests",
                "name": "CI-Tests",
                "detail": "no pull request found",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "no_data",
                    "params": {}
                  }
                ],
                "max_points": 2.5
              },
              {
                "key": "cii_best_practices",
                "name": "CII-Best-Practices",
                "detail": "no effort to earn an OpenSSF best practices badge detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "code_review",
                "name": "Code-Review",
                "detail": "Found 0/30 approved changesets -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "contributors",
                "name": "Contributors",
                "detail": "project has 0 contributing companies or organizations -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "dangerous_workflow",
                "name": "Dangerous-Workflow",
                "detail": "no dangerous workflow patterns detected",
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              },
              {
                "key": "dependency_update_tool",
                "name": "Dependency-Update-Tool",
                "detail": "no update tool detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "fuzzing",
                "name": "Fuzzing",
                "detail": "project is not fuzzed",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "license",
                "name": "License",
                "detail": "license file detected",
                "points": 2.2,
                "status": "partial",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "maintained",
                "name": "Maintained",
                "detail": "30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10",
                "points": 7.5,
                "status": "met",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "packaging",
                "name": "Packaging",
                "detail": "packaging workflow not detected",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "no_data",
                    "params": {}
                  }
                ],
                "max_points": 5
              },
              {
                "key": "pinned_dependencies",
                "name": "Pinned-Dependencies",
                "detail": "all dependencies are pinned",
                "points": 5,
                "status": "met",
                "details": [],
                "max_points": 5
              },
              {
                "key": "sast",
                "name": "SAST",
                "detail": "no SAST tool detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "security_policy",
                "name": "Security-Policy",
                "detail": "security policy file detected",
                "points": 5,
                "status": "met",
                "details": [],
                "max_points": 5
              },
              {
                "key": "signed_releases",
                "name": "Signed-Releases",
                "detail": "Project has not signed or included provenance with any releases.",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "token_permissions",
                "name": "Token-Permissions",
                "detail": "detected GitHub workflow tokens with excessive permissions",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "vulnerabilities",
                "name": "Vulnerabilities",
                "detail": "1 existing vulnerabilities detected",
                "points": 6.8,
                "status": "partial",
                "details": [],
                "max_points": 7.5
              }
            ]
          },
          {
            "key": "high_risk_jurisdiction_exposure",
            "band": "excellent",
            "name": "High-Risk Jurisdiction Exposure",
            "note": "Only high-confidence self-published location evidence affects this multiplier. Ambiguous matches are review-only; country evidence is not proof of nationality, citizenship, legal registration, malicious intent, or sanctions status.",
            "notes": [
              {
                "code": "jurisdiction_evidence_limits",
                "params": {}
              }
            ],
            "value": 100,
            "inputs": {
              "meaning": "self-published location evidence; not nationality or citizenship",
              "red_flag": false,
              "exposures": [],
              "policy_countries": [
                "Russia",
                "Iran",
                "North Korea"
              ],
              "review_only_matches": 0,
              "assessed_self_published_locations": 1
            },
            "components": [
              {
                "key": "policy_exposure_multiplier",
                "name": "Policy exposure multiplier",
                "detail": "no confirmed policy-scope location match",
                "points": 100,
                "status": "met",
                "details": [
                  {
                    "code": "jurisdiction_no_match",
                    "params": {}
                  }
                ],
                "max_points": 100
              }
            ]
          }
        ],
        "description": "Are visible security and supply-chain practices strong, with no malicious dependency and no unresolved high-risk jurisdiction exposure?"
      },
      {
        "key": "ai_readiness",
        "band": "moderate",
        "name": "AI Readiness",
        "value": 64,
        "weight": 0,
        "metrics": [
          {
            "key": "ai_agent_context",
            "band": "at_risk",
            "name": "Agent context & guidance",
            "note": null,
            "notes": [],
            "value": 40,
            "inputs": {
              "has_llms_txt": false,
              "legible_history_share": 0.98,
              "agent_instruction_files": [],
              "agent_instruction_max_bytes": null
            },
            "components": [
              {
                "key": "agent_instructions",
                "name": "Agent instructions",
                "detail": "no CLAUDE.md / AGENTS.md / editor rules",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "no_agent_instructions",
                    "params": {}
                  }
                ],
                "max_points": 45
              },
              {
                "key": "machine_readable_docs_llms_txt",
                "name": "Machine-readable docs (llms.txt)",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 15
              },
              {
                "key": "legible_commit_history",
                "name": "Legible commit history",
                "detail": "98 of 100 human commits state their intent (structured subject or explanatory body)",
                "points": 40,
                "status": "met",
                "details": [
                  {
                    "code": "legible_history",
                    "params": {
                      "legible": 98,
                      "sampled": 100
                    }
                  }
                ],
                "max_points": 40
              }
            ]
          },
          {
            "key": "ai_verify_loop",
            "band": "good",
            "name": "Verify loop (build / test / typecheck)",
            "note": null,
            "notes": [],
            "value": 71,
            "inputs": {
              "has_nix": false,
              "has_tests": true,
              "lockfiles": [
                "go.sum"
              ],
              "has_dockerfile": false,
              "typed_language": true,
              "bootstrap_files": [
                "Makefile"
              ],
              "has_devcontainer": false,
              "has_linter_config": false,
              "typecheck_configs": [],
              "agent_commit_share": 0,
              "toolchain_manifests": [
                "go.mod"
              ],
              "dependency_bot_commit_share": 0
            },
            "components": [
              {
                "key": "one_command_bootstrap",
                "name": "One-command bootstrap",
                "detail": "Makefile",
                "points": 18,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "Makefile"
                    }
                  }
                ],
                "max_points": 18
              },
              {
                "key": "automated_tests",
                "name": "Automated tests",
                "detail": null,
                "points": 22,
                "status": "met",
                "details": [],
                "max_points": 22
              },
              {
                "key": "lint_format_config",
                "name": "Lint / format config",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 11
              },
              {
                "key": "static_type_checking",
                "name": "Static type checking",
                "detail": "Go (statically typed)",
                "points": 11,
                "status": "met",
                "details": [
                  {
                    "code": "statically_typed_language",
                    "params": {
                      "language": "Go"
                    }
                  }
                ],
                "max_points": 11
              },
              {
                "key": "reproducible_environment",
                "name": "Reproducible environment",
                "detail": "lockfile",
                "points": 10,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "lockfile"
                    }
                  }
                ],
                "max_points": 10
              },
              {
                "key": "demonstrated_agent_practice",
                "name": "Demonstrated agent practice",
                "detail": "no agent-authored commits among the last 100",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "no_agent_authored_commits",
                    "params": {
                      "sampled": 100
                    }
                  }
                ],
                "max_points": 10
              },
              {
                "key": "automated_maintenance",
                "name": "Automated maintenance",
                "detail": "no automated dependency updates observed",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "no_dependency_automation",
                    "params": {}
                  }
                ],
                "max_points": 8
              },
              {
                "key": "openssf_scorecard_pinned_dependencies",
                "name": "OpenSSF Scorecard: Pinned-Dependencies",
                "detail": "all dependencies are pinned",
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "ai_code_legibility",
            "band": "excellent",
            "name": "Code legibility for models",
            "note": null,
            "notes": [],
            "value": 96,
            "inputs": {
              "primary_language": "Go",
              "largest_source_bytes": 95672,
              "source_files_sampled": 133,
              "oversized_source_files": 9
            },
            "components": [
              {
                "key": "type_checkable_code",
                "name": "Type-checkable code",
                "detail": "Go (statically typed)",
                "points": 45,
                "status": "met",
                "details": [
                  {
                    "code": "statically_typed_language",
                    "params": {
                      "language": "Go"
                    }
                  }
                ],
                "max_points": 45
              },
              {
                "key": "manageable_file_sizes",
                "name": "Manageable file sizes",
                "detail": "9/133 source files over 60KB",
                "points": 51.3,
                "status": "partial",
                "details": [
                  {
                    "code": "oversized_source_files",
                    "params": {
                      "kb": 60,
                      "sampled": 133,
                      "oversized": 9
                    }
                  }
                ],
                "max_points": 55
              }
            ]
          }
        ],
        "description": "How well is the repo equipped to be developed and maintained with AI coding agents? An independent, experimental badge — weight 0.0, so it is surfaced on its own and does not affect the overall health score."
      }
    ],
    "metrics_version": "1.13.0"
  },
  "warnings": [
    "Star history unavailable: GitHub GraphQL error: Resource not accessible by personal access token",
    "GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository"
  ],
  "report_type": "repository",
  "generated_at": "2026-07-28T06:42:31.984813Z",
  "schema_version": "0.27.0",
  "badge_url": "https://raw.githubusercontent.com/inspect-software/badges/main/v1/r/runos-official/cli.svg",
  "full_name": "runos-official/cli",
  "license_state": "custom",
  "license_spdx": null
}

Las puntuaciones son señales, no garantías. Reflejan prácticas públicamente visibles en GitHub; no son una auditoría de código ni una garantía de seguridad.

Los datos ausentes se excluyen y los pesos se renormalizan; nunca se puntúan como cero. La metodología es versionada y abierta: métricas v1.13.0, esquema v0.27.0 — metodología completa · wiki de métricas.

Cómo se sitúa un resultado dentro del registro general: estadísticas agregadasGo.