原始 JSON 报告 机器可读
{
"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
}