diff --git a/AGENTS.md b/AGENTS.md index 72d3ec30..8e07877f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,7 +2,10 @@ ## OpenWiki -This repository uses OpenWiki for recurring code documentation. Start with `openwiki/quickstart.md`, then follow its links to architecture, workflows, domain concepts, operations, integrations, testing guidance, and source maps. +This repository has a generated `openwiki/` evidence index. It is optional just-in-time context, not required startup reading. + +- Treat source code and tests as authoritative. A brief's unknowns and review items are verification gaps, not automatic requirements. +- Prefer the narrowest quiet validation that proves the changed behavior. Preserve complete failure output. The scheduled OpenWiki GitHub Actions workflow refreshes the repository wiki. Do not hand-edit generated OpenWiki pages unless explicitly asked; prefer updating source code/docs and letting OpenWiki regenerate. diff --git a/CLAUDE.md b/CLAUDE.md index ae5d420f..872ed7e0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -104,8 +104,6 @@ For current architecture details (tabs, schema, event loop, modules), see `openw ## OpenWiki -This repository uses OpenWiki for recurring code documentation. Start with `openwiki/quickstart.md`, then follow its links to architecture, workflows, domain concepts, operations, integrations, testing guidance, and source maps. - -The scheduled OpenWiki GitHub Actions workflow refreshes the repository wiki. Do not hand-edit generated OpenWiki pages unless explicitly asked; prefer updating source code/docs and letting OpenWiki regenerate. +See [AGENTS.md](AGENTS.md) for OpenWiki agent instructions. diff --git a/openwiki/.claims/architecture/data-model.json b/openwiki/.claims/architecture/data-model.json new file mode 100644 index 00000000..686ba2b7 --- /dev/null +++ b/openwiki/.claims/architecture/data-model.json @@ -0,0 +1,398 @@ +{ + "schemaVersion": 1, + "pageVersion": "sha256:1b2fff0cf6ba01402642dcd849e24063d2d277d6bc157b3cae6b0bb15a38fe91", + "claims": [ + { + "id": "claim_991711a614e24d978b915d30fe4e656b", + "statement": "SSHub keeps two SQLite databases in the profile directory: launcher.db (schema-managed store for hosts, groups, identities, tunnels, auth events, snippets, log bookmarks, and UI state) and metadata.db (legacy per-alias host_metadata). metadata.db predates launcher.db and still backs per-alias metadata for hosts that exist only in ~/.ssh/config.", + "evidence": [ + { + "resource": "repo://src/metadata/db.rs#L8-L18", + "version": "repo-lines-v1:sha256:077066a3108982f2ec4abc1b545ef7c9f68e1c6c952785e464f819b729bda800:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjZiMDZkNTU5NGI1OTQyMTM2YmY1Y2QwODYyNjVhN2I5NmM0YzEzYmIwODhhM2Y4MzI1YzVkYWIyZGQxNzRjNGUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ0ZWM3YTgyYjNjOTNlZTcwZDMzY2E5NTkxZDA1M2E2YzFlZjc2NmY4OTYxOGIyZDA1NDkyZDNjMzFiMjM5ZDIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjBiY2I1NTRkMWZhOWRkNmNhYmY1YzE0ODBlZjRlOTFkNjA3YzJhY2RjZTA0MmY3M2JiYjkyYjdiMzRmZTYxMTAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjFkMjIxM2ZhNDEzMTMwMzRjN2E5ZjJmNDdkMGU4MjczODI5YmQ3NDY1NGFlMzUzODBhN2FkZGMzYmMwZWIwYTcifQ" + }, + { + "resource": "repo://src/profile/mod.rs#L1-L16", + "version": "repo-lines-v1:sha256:27466cc50afc29811c0f5987a4c14e1cbd6f0f81ba836fe96625a5093c576428:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImYyNWMxMmUyMTVjNmJlODZkYTM3YWRiOGI0ODU5MDI2OWUyNjU2Yjg3OGZkMWFhM2RmMjQ4OTlkNDZlNWEyNTAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijg1YmZjNWE3ZWQ3MGI5NTM3ZDAwNDM5MDk2YmYyZDlmYWM0OTk0OTYzM2MxN2I4N2U4MjRmYzlhODI2NGIwY2MiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjY0NzMxNTFkZjhkZTNiMDBjYjllMGQzYjJkOTk4YThmM2ZlM2M0YzgwOGExODM2ZTliMTE5N2Q3NzQ5ZjljMDEifQ" + }, + { + "resource": "repo://src/store/mod.rs#L22-L28", + "version": "repo-lines-v1:sha256:6441e976a1c6f80fa7fc4ea512d4c2b215ed022cdcbf7196698123ad2a4f5b9f:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNWE1ZTJiYTM1NmU3NzE4MjYxN2M1MTYzOTAwNTA0NWQ3MDY1YTliYjQ4Y2E4ZDRmYmVjZTFmNDVjNTRhZTMwMSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNjM5ZmZiOTMxOTM5N2RiNjM3YTAyMTIzYmIwNzMzOGZlMGI2OGI0NWIzZDBhNzhlNDFhM2FiNmEzZjFlOGMwNyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiYjQ0Njg3ZTQ4ZTAzMzRlZTI3YmQ3MjBmYjliZGI5YjdhNWNhN2ZiODYzOTQwZWE2ZDhkOTNmYzI0ZDY2NTlkNSJ9" + } + ] + }, + { + "id": "claim_eb5c22e60bc941369c7db5b98e26b3af", + "statement": "launcher.db migrations run as stepwise migrate_vN_to_vN+1 functions up to SCHEMA_VERSION 15, all inside a single transaction so a crash cannot leave a half-upgraded schema; column adds are guarded with pragma_table_info so re-runs are idempotent.", + "evidence": [ + { + "resource": "repo://src/store/migrate.rs#L251-L263", + "version": "repo-lines-v1:sha256:9cd482c9ed9ca8dfb44ccabe5b72713238fc006905670e2d64a8ab80b6965807:eyJzZWxlY3RlZExpbmVDb3VudCI6MTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjMyYWZkNzAzOWUxNzA5NDg1NWM2NWYyMGRmODRhMzQwNDNkNWE4ODljOTJkMmJjZmQ5NjdjYjU2YWZmMWVmODUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImZkYzZmYjQ1NDJiOWJkN2VjZWExOWVkNDE3NzkxMTcyN2QzYTEwMmNmYTM2MzAyMjg1OGFjOWZlYTA5OTRkMTcifQ" + }, + { + "resource": "repo://src/store/migrate.rs#L6-L6", + "version": "repo-lines-v1:sha256:2214105741d5228f565c19c8c51e33fed1a3a98c9fc09c76e64b737d465e7a86:eyJzZWxlY3RlZExpbmVDb3VudCI6MSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMjIxNDEwNTc0MWQ1MjI4ZjU2NWMxOWM4YzUxZTMzZmVkMWEzYTk4YzlmYzA5Yzc2ZTY0YjczN2Q0NjVlN2E4NiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMjIxNDEwNTc0MWQ1MjI4ZjU2NWMxOWM4YzUxZTMzZmVkMWEzYTk4YzlmYzA5Yzc2ZTY0YjczN2Q0NjVlN2E4NiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNTZlMGNiZTJkYjFkZTRmN2UzM2MxOGRmNjYxNmIxZTUxZjU2NzNmYTdhMDgyNDM5ZjA0ZjA3ZWE4MGYxMzc1YyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZGYxYzA2MTMyY2RjZmUyZDQxZmJkN2EzZjNkMzExOWM1ZjU1OWM5N2E0NDgwNjlkYTg2NDFiYzU5ZTE0MTRlNSJ9" + }, + { + "resource": "repo://src/store/migrate.rs#L70-L151", + "version": "repo-lines-v1:sha256:2e45f52acbc1fcf7b5821770bfbcd764b9018d4e5001f948c18312f0adc52243:eyJzZWxlY3RlZExpbmVDb3VudCI6ODIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjcyMTFkYmI5NTY1ZmQ3OWNiYWViZjVjNzU0ZjhkMjM5NTE3MzZlYzM2Nzk0ZjM4M2UyMTlmMmFjNjllNGUwMmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImI5NGVjNTljZmQwNDIwZDg0NDQ5YjFkOTI5ODEzMTRkZWI4ZTdhNjlkMjVlZDAwMTdlOWFiZjgyNDJiYjUyMWIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImIwYWYzZGNhMDlmNzhlYzkxMTdlMzRjYzk0NjljMGUyZmE0MTE0N2FmMjYyNTVlOGQ1ZWM3Nzk0YmIwMTA3M2UifQ" + } + ] + }, + { + "id": "claim_bfba606dee2940cfbbe20fc3bdfc5ed6", + "statement": "Migration v13→v14 creates the snippets table (command-snippet library) and v14→v15 creates log_bookmarks; a migration test pins the merge order by verifying a database jumping straight from v13 (or from fresh) to the current version runs both steps and ends with both tables.", + "evidence": [ + { + "resource": "repo://src/store/migrate.rs#L500-L530", + "version": "repo-lines-v1:sha256:e793eebb121663fc62780307726ab63bcf3a5618520bd1db0537b95c088b9e41:eyJzZWxlY3RlZExpbmVDb3VudCI6MzEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNmYmRhYWJhZDQwYTgyOWJlZWNmMTE5Y2FmYWQwY2I4ZmRlY2E1NmYxY2I5MDEyZjZhOGZhODEwYjkwMzM3MzYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjM1ZjZmNDY2ZTU5Y2ZjMzA3MDNjNDVmNzJhZjY1ZGU1NGFjMzYzZDM1NGNjMWEzMzMwMzUwYWNkNDUyNDM0YzQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjY2ZTA4MTRjZTMxOTM3NjZiNTllMGNmZTZlMWI1ODgzMGJkMjhhM2JmMDk4MDgwMTlkY2I2ZGVmMDc3YmFhMDkifQ" + }, + { + "resource": "repo://src/store/migrate.rs#L616-L649", + "version": "repo-lines-v1:sha256:d0c1ffa5bab209c8e7e5c22036f21774b25716c1657ab73266eb3833334b2312:eyJzZWxlY3RlZExpbmVDb3VudCI6MzQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImZhMzIwYzAzOWU1MjdmMjYwMjZhNWUwNTA2YTAwNmRkN2M4NWZlNDg4NDI5NDdjZGMzYjRjZWZiOTMxZTY2NDUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImEwMGRiMDQwZjI5MDliZjk3OGZmMGE5NTZhNTVlODNjNjUzNDE4ZGU2YmI2OGY4MDQxZGE5NzZmOGQzZDIyNzAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQ0YTM5MzIyNzk0YTdhYmE0NzQ5MjA5MTEwZjYwM2JjZDY3NjRiZmQ2MmNlMzRkNjc4NDMyMTU0ZWEwNjIyODAifQ" + } + ] + }, + { + "id": "claim_7e38a9ab70b940fbb190ea30dd63909d", + "statement": "log_bookmarks are keyed by the on-disk log location (host_dir + file_name + line) rather than a host row id, so a bookmark survives deleting and re-adding the host; CRUD is newest-first and stores a user-facing name (blank names fall back to \"line N\").", + "evidence": [ + { + "resource": "repo://src/store/log_bookmarks.rs#L8-L56", + "version": "repo-lines-v1:sha256:cfed3c256db6ad00e9d2b0c276b127f0876b97ea7f99dda64829271b592285cd:eyJzZWxlY3RlZExpbmVDb3VudCI6NDksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ1NzkyYmVhMmFlZjdjYTBjMTM1MGNiMmM4ZmZhZTA1OGEzYzg5Y2VmZDdjNjlhYjBlYWJjY2JmNzY1ZmI0MjEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjU0MTdlZmZjY2Y0MzQyMDE0Mjg2NzQ5ZmVmMmVjNDRhMTE5NWI5MmNjMmUzYTYyY2RmYTZhYWZmNTYxMjQwZTgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjdiYzk4ZDZiMGRjNDkyYzUwMTZkZjEwODA1YTNhNTRlYjljMTg0YTVhNjEzN2U3ZjRiZWNmYTBjMjIwNTUwZDAifQ" + }, + { + "resource": "repo://src/store/migrate.rs#L515-L530", + "version": "repo-lines-v1:sha256:efbf1f30f12d402c042ee432dc0c01732d54f4dce04de5c296b3782800f2996f:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImIwZGJjNjdhYWVhODFmNzZjYjcxZjZmMWM0Mzg3MjQzZmM5NjNiYjM4MGE4MmQzZDNlNGVjYjBkZjZmZDI5NDMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjM1ZjZmNDY2ZTU5Y2ZjMzA3MDNjNDVmNzJhZjY1ZGU1NGFjMzYzZDM1NGNjMWEzMzMwMzUwYWNkNDUyNDM0YzQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjY2ZTA4MTRjZTMxOTM3NjZiNTllMGNmZTZlMWI1ODgzMGJkMjhhM2JmMDk4MDgwMTlkY2I2ZGVmMDc3YmFhMDkifQ" + }, + { + "resource": "repo://src/store/types.rs#L323-L342", + "version": "repo-lines-v1:sha256:c33e438f71c6fe2e36b2c34fee5e37fa12409661d3472c80e64a3583e88ea76d:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImI1ODZmN2RlY2I2Mjc5Mjg0YmFhMmMzY2UwYTJjNmYzZTg0ZjgzZTY3OTRkNThjMzE1YzQ0OGQ3MjlkODYyZGYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjFlOGE4ZmQ2OWY1MmFiZTYxMzEyNWM2MWUwMTk2ODBlODdlODE5MDEwZWI4Y2RhNGY5NmI2MzQ3NmIxYjhkYWQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImFlNjY3ZGM3MzU3MTY3YjYyYjU3ODcxM2EyMDEyMjNjMzUyNmViNWJhZTZjMmNjOGRiMDQ1MjA1MGE5MmQ1ZmQifQ" + } + ] + }, + { + "id": "claim_6ffce12a512940248bc97d7fdabcebde", + "statement": "The ui_state key/value table persists collapsed_groups (JSON array of group ids), ui_zoom (with a legacy name_zoom fallback for upgraded installs), and sftp_show_hidden (SFTP dotfile visibility), so these choices survive restarts.", + "evidence": [ + { + "resource": "repo://src/app/hostlist.rs#L272-L286", + "version": "repo-lines-v1:sha256:e8b33e5b73c170d092c8cb89ae4848cd4902b240d940d3d5ac8fdf8eb047c81c:eyJzZWxlY3RlZExpbmVDb3VudCI6MTUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ5ZjFkOWI1MGNiOTMzNDhmYTk5Y2Q0YmIwODU4MmI1MzMxNzMzZWQ5MjI5MjQ0Y2U0YTUxNTlmYTQ0OTUwNGYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjBmNjg5ZTQ0MjE1NThiOTkwODVkMmNmZTQ3ZjUxNGQ0YWExMjIxZDVmZTZiMTJjNjcyM2U2ZWY4NzU4NWY3MjEifQ" + }, + { + "resource": "repo://src/app/mod.rs#L1203-L1225", + "version": "repo-lines-v1:sha256:fd9f2db7d05032791c1f247e8f694c7929aa973efc1b2459af528305a6d30441:eyJzZWxlY3RlZExpbmVDb3VudCI6MjMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2MGZjMzlmY2M0NGY4N2Y3ZGVhMjZjMmY3ZTM0Yjg3YWMzZjVlY2MzYWUyZmFlYTRmZWYwNjRlY2Y4ZjZlZWUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImNhNTM4MWI5NzA4YTg0NzRlOWQ5M2M1MWRlYzI0NzE5MzdmMGZkZDM1Njg2Zjk3NDM2NWY1ZTRjZjVkN2FmMTgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjcxMTBjYjI4OTBlZDIyNTBkOWE2YWJhYzU3NzQzZTNhMDM4OTQ5NWFhZWRkMThhZjk2ZTY0YWMxMDg0ZDNlMWEifQ" + }, + { + "resource": "repo://src/app/sftp.rs#L736-L765", + "version": "repo-lines-v1:sha256:1a17a8d063894bb342a53aa88d3f61d3bdde51827a1dfa1a308fe2dff2b1aa8d:eyJzZWxlY3RlZExpbmVDb3VudCI6MzAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjIzZTVlNjVmNTAxNDkyMjllYTM4Y2RmODczYzk4NTY5ZmIwYmY5NGU0MWNiMzNkY2NlZjE3NzgyYmZmOGEzYWMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE2MmVlNTdlNDFhOGMyMGFlMDE2NGRmMjYxNDM5ZGNjNDk5ZDM1MjA4MzQxNTE1MTZkMDQ3ZThkZWFlOGE0NjYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImM5NGI0YzA0YjEzYjg4YzU0MGQ5Y2EyNzIxMGNlYjNlYzI3ZmQxNDVkZjA4MmUwZjJhNjc0OWMzY2I4OTgzOTgifQ" + } + ] + }, + { + "id": "claim_78f5e22453fe42b28552b08abecbebec", + "statement": "The built-in Favorites group is marked reserved=1 and cannot be renamed (rename is a no-op) or deleted; the migration never repurposes a user's pre-existing group named Favorites — it creates the reserved group under a distinct name — and runtime favorite lookups go through the reserved flag (favorites_group_id selects WHERE reserved = 1; the connection-scoped helper additionally matches the name). Membership in Favorites is the source of truth for favourite status: reads derive favorite from reserved memberships and writes sync hosts.favorite into a membership row.", + "evidence": [ + { + "resource": "repo://src/store/hosts.rs#L68-L106", + "version": "repo-lines-v1:sha256:67c3da85b42be38d06a005e93b1c753b46026412eaf4cddffa8e863f6dff2aa3:eyJzZWxlY3RlZExpbmVDb3VudCI6MzksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjZjNTE0ZWE5MTg5NGE3MGNkZTU2YzE3ZTM0ODA0NjFiODg1Y2QyY2NlNTNmMjQ4YTg4ZTRkMTkyMjA5NmRhYTIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjMzMTA3YzU4YjJlMGI3YzQ1ZGJjMDY4ZDMyZDQ3YzE2YmRjYzQ0MGZhNmUwMjM5ZTE4OWQyMDNiNWZkN2JiZGQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjgxYWM3ZWFiOGY1NmMxMmJmNjNkNzhmZDA2NTIyODZiNGZjMWE2YjIwMjc5MjFkNThkOTkwZWU5YzYyY2NlNDgifQ" + }, + { + "resource": "repo://src/store/hosts.rs#L943-L943", + "version": "repo-lines-v1:sha256:4f7cf221af9531b2589f77d08ed4308d8d60f31ec011afe2c0bd7e91aafc08c2:eyJzZWxlY3RlZExpbmVDb3VudCI6MSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNGY3Y2YyMjFhZjk1MzFiMjU4OWY3N2QwOGVkNDMwOGQ4ZDYwZjMxZWMwMTFhZmUyYzBiZDdlOTFhYWZjMDhjMiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNGY3Y2YyMjFhZjk1MzFiMjU4OWY3N2QwOGVkNDMwOGQ4ZDYwZjMxZWMwMTFhZmUyYzBiZDdlOTFhYWZjMDhjMiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYjgzOTNlNmI0NDNjNjI4NjJhMzBhYTNhMjFjY2EzOTZmNTA5YzRmMDEyMWFjZTgyNzdjNTkwMjdmYzAzYTczNyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMWJjNWU5NDU2MDg1YjNkNThjNGUxMzZmZjBmOTgzYTFkN2JmODg0OTM2OWExZjgwOGFmMTlhNDAzMGEyZTQ0MCJ9" + }, + { + "resource": "repo://src/store/hosts.rs#L963-L993", + "version": "repo-lines-v1:sha256:1bfbedf0215344a09e88e2309e3ed4090503fcaead6438a406903d573a9acacb:eyJzZWxlY3RlZExpbmVDb3VudCI6MzEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQxMWM3OTc0N2Q3MjU3MDQwYWFkYTU3OTM3NmE1ZTkzMzBkYWUyM2Q3MTExZTRhMjlhZWQ0MmJjMzljYmQwNTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjU1NmY1MjRjMjE2OWM0MTgxYzE1ZTEwODljZjk4MGUyYzQ1Y2U0NmY4MTA1OWIwMGI0M2Q0Yjc2YWNiYTIxYTgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjVjYzViOGU1MmFmOWJiY2QzYTlmZTRhZWQ0MzBhNjZmNTk1YTM2ZWFlMDdjNjZkNmQxNWE0NGRkNjhkMzBhZGYifQ" + }, + { + "resource": "repo://src/store/migrate.rs#L419-L453", + "version": "repo-lines-v1:sha256:eb2cf3e00b51a48967040da858f58116bc5c7dd9a41cc2d81825d543a2ee6cba:eyJzZWxlY3RlZExpbmVDb3VudCI6MzUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjZjMzQ2ZTI5ODc4NGM0YmI0MGFkODcyNGEyOTRmZTBjOWM4Mzk1Nzc0Mjk2NmM0ZTk5MTA5YWM2ZTMwOWY3ZWMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFkOWUxMjU0NjQyNTU3NmQzZGVhZThjOWI5ZGJmMTI4ZDVkNmRkYWY1ZDdmZTg1NzkzNDkyN2RlZmZhM2I5ZGQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQ5ZGMwOGFkZjJkNDA1YjUzNmNjNWEzMThjZTM3MDUzZDhkNzUwMjI5OTU5YjVlNmM4MjMyNTEzYTJhMThmOWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjBhOTJjNTcwYjg2N2Y0ZjkwOTUwMjUyZGZkZGVmMjg0ZmExN2NjNjJmZjM3NWNiMjJhNDllODEyZmQ1NzZkMGEifQ" + } + ] + }, + { + "id": "claim_664056b326064a08b25ae1040937e7c9", + "statement": "create_host and update_host refuse a name, address, or username starting with '-' (is_option_like/reject_option_like, issue #101), because ssh would parse such a value as an option instead of a host; ssh::safe_ssh_target is the connect-time backstop that rewrites a leading-dash target into the ssh:// form OpenSSH refuses.", + "evidence": [ + { + "resource": "repo://src/ssh/host.rs#L88-L107", + "version": "repo-lines-v1:sha256:0855157bae5856cb010151d37ec33abc461233c80742b5d9cd9c0057cf0028b9:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRhZDMwNDVlODI0NDA2MmYyMzYwYTU3MzdmMTZmNDkxYmNmNDJlMjJkOTg5MWIyYTk1YmRhNTJlNTUxZWVkMGMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk3M2ZhMTMzYzY3NTU0MzE2NzIyZGI3YWNmNDI2NDAzNGNmZGRhMGQ5ZWNkZThhNWM0MjY3NjVkOTQxODA5YzciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImM1ODc1ZDMzNmI5NmVlMGNkZjdlNmJmYjg4YmU0NDRhYWQzY2MwMGZkZjg1Yjg3MmZiYjRkNTA2NTAyZTMyZWEifQ" + }, + { + "resource": "repo://src/store/hosts.rs#L204-L209", + "version": "repo-lines-v1:sha256:6ecb6543212b8103e776a20aa5aab2db644e93dfb1051ab712c13ad27b61a065:eyJzZWxlY3RlZExpbmVDb3VudCI6NiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYmYyNTU3MTUzZGFmYWVlNThmYzMxNTgwZTViODdjZmZmMzgxZjY0MTliM2E0NmMwMGZjMjA5YmE3MWExZmE0NiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZTZiMDQ0OTE0Njk3MjJlODM2M2E5NGU0MDQ0NTM4ZTAxM2EyYTIxN2UzNmIxZTU2OGJhNjBhY2I3YjRjOGYyZSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMWYyNDA4YTU3YWJkMTJhY2VjZDA0OGRjNzBlNWI2YjYwZWYxMjRjNjcxOGIzMzhmNzk2M2E0MjI4OWYxMWJjNCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNjEyMmFhZGQ3NGZkZDgxNWIyMjEyZWQ5OGZhMTMzMjNlY2YyN2EyYWFjM2QyOTQwN2RmNDZhNDZjZjEzNWRkNyJ9" + }, + { + "resource": "repo://src/store/hosts.rs#L887-L910", + "version": "repo-lines-v1:sha256:2c9942e77b2b2c22ec28529cd519206744a9585b8236d598359faf88787a4eed:eyJzZWxlY3RlZExpbmVDb3VudCI6MjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjYwOTNiMTNkOWQ0ODNhZjFiNGRlNDIwN2QyYjYzMjg0Y2ZjODE3MDRlMjk4Njg2MWM3MWRkOWFmNDg1YWY0YWQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImEwYTEyNjk5YzE4MDk4MDg0Mjk3OTNhOTNkZGU2MDcwYTU4Y2VlMzBmZmIxMmQ4MTEyY2FjNjI0MmU0YTI3NjEifQ" + } + ] + }, + { + "id": "claim_4e80461112594fe3ba81fa52b9ae154b", + "statement": "Secrets are never stored in SQLite — the schema holds only has_password flag columns; actual secrets live in the OS keyring behind NamespacedPasswordStore, which prefixes every key with profile:: so identical host names in different profiles never share an entry (empty prefix in compat mode, credentials.json fallback when no keyring).", + "evidence": [ + { + "resource": "repo://src/credentials.rs#L13-L44", + "version": "repo-lines-v1:sha256:0298f978608d0f982cc29cff910a71e284379e479087a9ae61b223ae8e37959c:eyJzZWxlY3RlZExpbmVDb3VudCI6MzIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAwYjM5ODE5MWJkMGViNmZkMTU4N2YyNzRkMzM5OTIyNzM3OGVlYTJlOTk1ODZiNjllMTk5NzE4YjQzOTZjZmYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjFjYWQ3MzBmNWM2OWU4OTAzY2FjNDhhZDE4ZjViMDE2NWQ4ZTk4NTk3MmM0NWRkZGEwM2M3ODA4NmViYTdlYjgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjRmNzdlNTAwMzNiNjE3ODIwNzc5ZWZlMDRmN2YzMjY0NTEzOWQ1ZGI3OWIxMmUzMGFkYTRmNWVkM2FlYmEyMTgifQ" + }, + { + "resource": "repo://src/profile/mod.rs#L291-L299", + "version": "repo-lines-v1:sha256:8c82f0f66a861b4405706a5dec31015bbcf11cc973726b677beaadaa3b4bcfa4:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZGMyYzA5NWIwNjQxYjhkNzFlODE1ZmYxYmJlNWY4NzhiMjU0OWMxNGFkNTczYmU1MjYzNWE2ZjlkZjRjY2Q3MSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYjY0MzRmYWQwZDI4YzZkOWVkYzg3Y2IzYTIzMDU5YTU2NDcwZDdmNjU5ZmFkN2Y3MGYxODA4NmI4ZDY3YTAxYyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZTBiMmMzZmE5MzM2MDcyODU4MmQ0NjZlZDVlYTMzODNiZjk3MThiMzUxNDQyYTg2MzlhNDI2MmVkN2ZlZWE0NCJ9" + }, + { + "resource": "repo://src/store/migrate.rs#L25-L56", + "version": "repo-lines-v1:sha256:f9578d9ad38acb50e62a5ac73cf5a2723b1fb900582d22adb3c67fa3fc938e3f:eyJzZWxlY3RlZExpbmVDb3VudCI6MzIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRmNTk3ZDU4YWI3NGI5YjVhYTQ4MGQ3NWZiNjg2Zjc1MzRlYzUxODBlMzU1N2YxNTYxYTk0Y2JkODZmZWNiZjEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ5OTY3ZWMxZWQzODM2ZjhlYjE3YWZmMTcyYjViZmVmN2RlNjA0ODVhNDM5YzZjYTExYzU5MWUxNzU1NzM5YWUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImI3MjQzZjc3ZjcwYmJlOWJjZDgxOTY2MDNkOGMwMWQ5YzUwZWI4ZjM3ZjFlYjUzNGNjZjhkOWJmNDcxN2VmODUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjBiYjgzNzkxMDVhY2NkYWEyYjMyZGE1OTAyY2EzYTRiNGE5MjVkN2JiOGQ0ZjEwYjU5Y2ZkZTEyYmVhOTgwZTUifQ" + } + ] + }, + { + "id": "claim_85d9adee40bd498fbb0e8a13d6aa06c6", + "statement": "The legacy metadata.db import runs only when the launcher.db schema version is 0 (fresh database), inserts rows as source=ssh_config hosts, and is best-effort: a corrupt or locked legacy DB is skipped with a warning instead of failing the migration (which would roll back the schema and brick later launches), and a corrupt tags blob degrades to no tags.", + "evidence": [ + { + "resource": "repo://src/store/migrate.rs#L138-L146", + "version": "repo-lines-v1:sha256:6e5bc6a9a3ac54cc3dfa480c7eef9c8d09dc667400ecafc8ec617df7fd6e5487:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiOWNjNGE0NzM0OTYwMTY1YjU3MzU5NmIzMWJkOTc1ZWJlYzc1NzRkNmU3M2YzOTY1NGQ5ZTg0M2ZiZWY1YzY4MiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNjUxN2IzZDNmM2M0NmFmZDUwZjNhYTBjZDkzZjNlNjM3NDMzMTBlZTQwNGFlYTBlZGY5NmM4ZTE0ODE1OWM5MSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZmZhYzc4YTA1NjU0ZWFlOTMzNDg5NmRjNjVlNmNkOTc4OWYzYjZlZmMwODg0YmU1M2JmMGIzYmYwNTNhNWZiYiJ9" + }, + { + "resource": "repo://src/store/migrate.rs#L209-L233", + "version": "repo-lines-v1:sha256:b40c6da24a67af2cbabcdd67d24f99f9f70368e701c6b32ef894c6f1eb0425b6:eyJzZWxlY3RlZExpbmVDb3VudCI6MjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU0NmU3Mzg2MDhlYWVmZjMwZDQwZjE1ZjI2YmVlZmY0ZTRlZDAyNzZiODZmYWEwMmUxODZlY2IxYjMwYzIzZDMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ4OTc3MWZlMWI2NjI4ODc1YWEyM2U3ZGJlNGFhNzE0NjFkMDBjZGZiNjZkZGYxMWI5ZDgyMGFjNTg5N2ZkZjYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImNiOTZhMzdmMTZiZDg2ZDljOGFlOWVjMDQ3Y2JiNmM4MWU0ZjAwZGNmY2U3NGQyOTRhM2I5OTdkYWFmMTdhMzAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImY3NDdlYjBlZGQ1ZWNkNWFlODM4NGQyMDJjM2M2NzhmZTYwODQyOTAzMmEzYzI5MTMxMzBiMzYxMjg1MzQ4NTkifQ" + }, + { + "resource": "repo://src/store/migrate.rs#L710-L739", + "version": "repo-lines-v1:sha256:ed6778573747878bcb278235ea07a0723784ef5f0e6d252664e4cf2a353bac67:eyJzZWxlY3RlZExpbmVDb3VudCI6MzAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkyYjVlZGY1OTQ5Y2ZlYTY3ODIyY2Y2OTExMzIzNjNkNjhkNmYxZTE2OWY1ZTBkNGY3YzE0ZTVmYTFlNmQ0ZTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjRhOTE0NmZmMmIzYTk4ZTQ4Yjk3YjYxZDc5YWIwNmE2MjhjMTEyM2E3M2IyZDk1YmU4NzliZTM0ZThmNjJmZjciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjEwYTFiYTU4MDQwMDI2ZTdiYjBkMmY0MjYyZGY4NzJiOTdmMjYwYzg1ZTlmMTAxMWZiYjhmYjZkMDYzMjBkMTcifQ" + } + ] + }, + { + "id": "claim_ed14f851776e4529a656ff104ad00aca", + "statement": "LauncherStore::open_in_memory points the nominal launcher path into a throwaway temp-dir location instead of ':memory:', so the legacy metadata import cannot silently pick up a stray ./metadata.db from the CWD during tests.", + "evidence": [ + { + "resource": "repo://src/store/mod.rs#L61-L74", + "version": "repo-lines-v1:sha256:f3d32409211e2593edbc0384e1f8b67a14508559d50595cf0dc9bda2fc5e795e:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNhZDc0ZThlM2E2NjA1YWNlMGVjYjdhOWIwNjRiZjE4MzgyZDRjMWFhYWYzMDljOTZjY2E2NDdjY2ZlZDBiMTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImIzODY0OWM0MDdiMGIzYzQyMjdmNzdhYzdjYzUxNzBjOGZlOWRhYjlhZDFhYTAzNzcyMjQxMzI0ZTAwMzFlMzgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE2ZWZiOTI4OTkyZGUyMmFlZTUxOThmODYxOGM0YTQxZmM2YzdlOGFlOWQzZjk1NzI2NzM3MGU3NjAzN2NiZDQifQ" + } + ] + }, + { + "id": "claim_792508d3ada3456ca0aa441aebc8bef7", + "statement": "`sshub db purge --yes-i-am-stupid` (confirmation flag required) deletes the selected profile's launcher.db plus its -wal/-shm/-journal sidecars; ~/.ssh/config and imported hosts are untouched (they reappear on next launch) and keyring passwords are deliberately orphaned.", + "evidence": [ + { + "resource": "repo://src/lib.rs#L70-L93", + "version": "repo-lines-v1:sha256:c6d09b2f202f7cde9a970c98f8d3b2830cd9fedd1bc53e15d3d7565ec0ebc474:eyJzZWxlY3RlZExpbmVDb3VudCI6MjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImIwN2JmYzEwMTI4ZjU4YTc0NDAwMjAzZTQ5MzZiZTU1Mjc5MGZkNmUyMmU5ZjU5ZTEwYjFkZDQ0M2QyNzIyYjciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE2MjJiNzAyOWRjYTAxNmJhZTI5ODZmNWU4MDI2YjNkM2E1ODYwNGM3NjM1ZGQ3OTRiYmQ0YTgyM2MwZWRlMDEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImVmYTY4ZGFjOGQxMjA5ODVkMGE0OWI5NTYzNjRhMmNlY2UyNGZkYmQ4YTJkMTY3ZjBjM2E3NzI1ZmNkMmIxY2IifQ" + }, + { + "resource": "repo://src/main.rs#L117-L148", + "version": "repo-lines-v1:sha256:53613117a219ad6434485707e8a91abe397522de7620c1d1eddde1bef6aa1407:eyJzZWxlY3RlZExpbmVDb3VudCI6MzIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE4Y2FjMzg5NjExOWM4MDczYzU3OTBmYmE3MWYwOTlmZDFlMjgxYjUyNTY2ZmEzZjhiZTNlNDFkMjZlN2U4ODEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjU5ZDZhNTBhZGY4NTc1NzFmOTgwM2E5M2U0YjM3ODUxOTQ2Yzg5MGE1OGRjM2UzYjIwNTRmMjVmMWJkYjQxN2UifQ" + }, + { + "resource": "repo://src/main.rs#L5-L6", + "version": "repo-lines-v1:sha256:426d89d1d5a7930f7ef39d554a692b1143a6f1fbe497b49973e3edca8d24dd13:eyJzZWxlY3RlZExpbmVDb3VudCI6MiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiODMxYThhY2U0MGYxZmMzYmFkZWIzMjcyNWRjMWE1MzQyMzgyNzYxODEyNTFjYTRkM2RhZTk4ZGU0ZTkxY2Y1MCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYmNhMGMyNTMyYmU0ZTgyZjIzNGExZjJhZjgzMDdjMTQ3ZWI2YjZlZmQxNDc4MThjMDkyY2VjYjgyYjQ0M2Y4YiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZmRlZjVmMDgyMDM3MDBjMmRmNjcyZDdmYTNlYjFlMGE5MjNiN2UwYTBiOTcxYjAzOWYxZGY3YjI5Y2ExMDdiOSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiM2ZhZmI0YmE0NDBlNzZkNzRmOTA5NTFjZjg3MDk1N2I3OGY2Mzc4OTAyOWVjM2UxMzUwYmRlOTVjNWRhOGE2YiJ9" + } + ] + }, + { + "id": "claim_e9ad7815b86b4513a99e118ced207fb3", + "statement": "load_merged_hosts builds the unified list in three tiers: launcher-source DB rows, ssh_config-source DB rows re-synced only when the resolved ssh_config_hash differs (launcher rows are never overwritten; unresolvable rows are left untouched), then remaining ssh_config aliases as read-only HostEntry::Legacy with metadata.db defaults ensured.", + "evidence": [ + { + "resource": "repo://src/hosts/loader.rs#L10-L54", + "version": "repo-lines-v1:sha256:186ec2bb2a0e4126f1898762830bd66db524d9f840edb46fab9b5c19231f2697:eyJzZWxlY3RlZExpbmVDb3VudCI6NDUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRmNDQ0MGJiNjdlNjllNTQ2YzJhYTU4NzYyZjdmMDhkMWVhMWM2NGNlMjcxOWY5ODhkNWRkZjBiZTk5OTAxNTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjAwMzk1MTkxNDk3NjY4YzVkNGRhZDA5MGIzZjY2ZTJiYjVkNDRjY2M4YWRkZWIxYzUwN2UzYTk3MmVmNWVhOWQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ" + }, + { + "resource": "repo://src/ssh/import.rs#L24-L50", + "version": "repo-lines-v1:sha256:95b31c735c35192941f4cb1d5706204b053386c6b76649f58ac6b66887854b6e:eyJzZWxlY3RlZExpbmVDb3VudCI6MjcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ5ZGQzNWViMTI0NzQ1ODdkNDc3ZjliZjA2ODRkZTYxZTU3ZGI2NDBlMWUwYTE5MjY4NWU2NjA5ZTBkMDFkMTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQ1OWNkYmFlYTM3NjJhNTFkZTQ5MTZmMTBjNjM5NjMwOTlkNmNiNGU4MmI3ZjM5ZmE2Mzk5NDdiYWIwMjhmYTciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjY4MGYyMzQ1NTBhOTE5YWVhZmYzYmVmYTkwNWVjYTMzOTQ0ZDk2ZWE0NGRkNjdjYzQwMzhhZGE1ZWE2NmY5MzQifQ" + } + ] + }, + { + "id": "claim_1f91e054fe25431d8fecca722d0cb9d8", + "statement": "SshConfigResolver parses Host aliases itself (excluding wildcards/negated patterns, following Include directives depth-capped at 16) and shells out to `ssh -F -G ` for effective options; the user's ~/.ssh/config is never written — export renders to exported.conf via an atomic write with a .bak backup, and conf_val flattens CR/LF so a host field cannot inject a `Host *` stanza.", + "evidence": [ + { + "resource": "repo://src/ssh/export.rs#L104-L128", + "version": "repo-lines-v1:sha256:7f5cc0f562e75ccd8a011ba533a3270a35521d8fb5f85732cd161251ac32154b:eyJzZWxlY3RlZExpbmVDb3VudCI6MjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNhYWFhYWY3OWIwMThiYzJhYjk3ZDMzZWE1ZWM5MDViYzk1ZGZmODllNDc3M2E1YzhlMGU2ZDZmNzY1ZGMwZTYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjM3OTdlYTQzYjc4N2U2NmY1MmRmNTg5M2QwZTU3YTA1ODliZjhhM2RmODJlM2EwZTkzOTJlYTgzODFlYWYzMTgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjdiYzk4ZDZiMGRjNDkyYzUwMTZkZjEwODA1YTNhNTRlYjljMTg0YTVhNjEzN2U3ZjRiZWNmYTBjMjIwNTUwZDAifQ" + }, + { + "resource": "repo://src/ssh/export.rs#L45-L50", + "version": "repo-lines-v1:sha256:22f5cac172c8f9c7ac1b43c5a2ca0a24cc9365d89e2f32e34ee7db443266a4a2:eyJzZWxlY3RlZExpbmVDb3VudCI6NiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNDU0NzNkYjYzNDAwODU0MTY4NmIzYWY4MDZhMWE0ZTY1YmI3Nzc4MjE2NzUyODNkYWU4YjVkZTY3ZmZkODEyNSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZjVhY2ZkYjgwMjVmNjA1MjBlNTcxNmNmNjc4ODU5YmVlMzliMThlMjZmMzVhZWYxNWMzMjlkZGIxNzA3OWYyMCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMTU0OTgzYjAxMDUwYzY3MTkxNzYwYjliODQxZDkzODliMGRhMjdlZmNiM2Y3OTViZWUxOGRmYTFjYmFmM2NkNSJ9" + }, + { + "resource": "repo://src/ssh/resolver.rs#L123-L155", + "version": "repo-lines-v1:sha256:43388c36c88ef5dc22ad01d42a86bf3171b875fbfe08f8c93b4425a53ec1b0ab:eyJzZWxlY3RlZExpbmVDb3VudCI6MzMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNhOTMyNGY3NTIzYmY5MWMxZmU1YjkyOWY5NWIxZDViMTVkZjAwOTM2MDU3NmJiMzRlNjFmNTJhMWQzZjkwYjQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImNjM2QxNjEwYzJlMDVmOWFlYjk3OThiMTI1NGU0Nzk5ZTdjODdjMjBjZDViNmQ4ZDdmMWE4NDljMDMyMmNkNWIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjczNGJhOTYzMGIzNDhkOGMyMGUzOWNiZWY5ZWYwYjY5YzM4YmQ1YTY0MzMxZDkyMzE2YjAxNGY4OWMyYzk1ZDkifQ" + }, + { + "resource": "repo://src/ssh/resolver.rs#L45-L72", + "version": "repo-lines-v1:sha256:063b8ea52a4a9ec5d037df5f5ad910f732ead14f1ec82b3dc2ec7c438b74f70b:eyJzZWxlY3RlZExpbmVDb3VudCI6MjgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFiYzhiNDUzZjJiZDMyMzQ4NjIwZjc4NzhjYjNiYjlkMGJjNmNiOTcyNTFlN2IzMWU5M2IzNjk1NzFiYzg4YTkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJkM2VkNjI2NGNiYTM4NTliOTM0N2RjZmEzNGU0Zjk5MjA2YmQ2MzZhZGQ0NjkzMjRkYjM1MTVlNWM3YjUzMTIifQ" + } + ] + }, + { + "id": "claim_6ab4f3b587f34451925b05c96b0790ce", + "statement": "[appearance] now holds active_theme (default \"default\"), the two independent transparency switches transparent_sshub_background and transparent_session_background (both default off), plus show_detail_panel, date_format, disable_animation, confirm_quit, identity_columns, and os_logo; the retired opaque_background key is ignored and dropped on the next save — recognized as a real key, so comments or lookalike names never trigger a rewrite.", + "evidence": [ + { + "resource": "repo://src/config.rs#L417-L450", + "version": "repo-lines-v1:sha256:c6f0b49d721f1dd37b7675113d6d4fa2ce56251e1b4190c9c09feceb4fd6fbae:eyJzZWxlY3RlZExpbmVDb3VudCI6MzQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjJlNTg0NGRmNTg3YzhkNTBkYmJhMWQ5NjdkNDVhZjEzODFiYmY2N2ZlMGJiYjFmMTkxOTU3YTg1YjI0MjQ5M2IiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk4Y2NmMDk3NjI2M2M5NGQ4NTZhZWE3MDBlOGJkZTRlMmI2ZmRkOGViNDQ3OTI5OGYwMzcwZjA3ZDU2OGRkMmQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQ0MjNhZDUwNmFlOWVlYzRjMjkwOWVlOTYzMWI5MGI1YmNmNmM1ZjRhYjBmMmU4YmIyOGVjYzhjOTBlMjQ1NzcifQ" + }, + { + "resource": "repo://src/config.rs#L7-L65", + "version": "repo-lines-v1:sha256:d0ad03f39c9fc36ff3ca58e6766d13a399e138a3dd020aa138b721b87da6082a:eyJzZWxlY3RlZExpbmVDb3VudCI6NTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjljZDhjZDQ3M2EwZThhNTI5MjUxNmJlMGE4Y2NmY2JkZWM0ZGNkNWRlZmU0NmI3ZTgzMTBmMjUxMjliNzUyMTQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjcyNzVhZjI5NTk0ODM4MTFjZjk5NzI3Yzc4MjZiNmY4MTIwMzkzNWI0MjFkZDhmNThiYzc4YWRhZTBlYjBhM2EiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjkwMDViNGJhMWQ1YjBhMmEyMmY2ZTdiNGY4OGIxZDZlYTFkYjU5ZjRkZjgyZDdmN2E0Yzc3ZTZlNTM3ZTc4ZmYifQ" + }, + { + "resource": "repo://src/config.rs#L768-L817", + "version": "repo-lines-v1:sha256:fd140aacb8bb0cf74f501461871b67bc41adce92c361f041897888f0ddb56290:eyJzZWxlY3RlZExpbmVDb3VudCI6NTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU1YTBmY2Q3Yjg4YzBkYWM3MDFiOGE2YjY1MWJmOTM3Y2NkY2E4NTU5ZDk4ZTllZDMyZTRkYWUyOWEyMjE0MTYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImEwMGRiMDQwZjI5MDliZjk3OGZmMGE5NTZhNTVlODNjNjUzNDE4ZGU2YmI2OGY4MDQxZGE5NzZmOGQzZDIyNzAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjliYzkyZWQ1ZmRiM2Y1NmZkNWY0MmI4Yjc3NmFjZTY3OTllYzNmM2ZkZGU4OTQ4MGFmY2NhZjE5MjRmODEwNGEifQ" + } + ] + }, + { + "id": "claim_fdd9c70104114a0c9ddc15eebb349f72", + "statement": "Legacy top-level `terminal` and `launch_command` keys from pre-0.10.0 configs are silently ignored (no deny_unknown_fields) so the rest of an old config.toml still loads; [clipboard].relay_from_pty defaults to true so configs without the section keep the OSC 52 relay.", + "evidence": [ + { + "resource": "repo://src/config.rs#L100-L108", + "version": "repo-lines-v1:sha256:618102a62458ea6d7960e6683350a226d5e7ad0c515a8b19cd51823f6f6769ac:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMjZiZGZlY2Y2OGJlYzNjZGE2ZGVmYjAyZWNjMjViMzFiYzZhYjgyNmNiMWNiNjg2MjE4ZTA1MDE3NDk1YjZhMyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNjcxZTdlNzVhZGQ3MjYzYzQ4ZDE0N2NlMjliYjhjMDA5NjlhMzM3N2I1YWNmMWU3YzJlMGExMGZiNTBmMGJlZCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiYzlkNDFiM2RmMDkwYWM4OTYxMDU1NTVlMTU0YzUyNmVlNjM4NDA3YTFhNjBjNTYzYmRjNGRkMTcyYmI4MmEwYSJ9" + }, + { + "resource": "repo://src/config.rs#L1201-L1217", + "version": "repo-lines-v1:sha256:9c86261a3e742e2c3ed02e230d7ead07cf83af4cd23ba43323a33ff6933c3ab3:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkyYjVlZGY1OTQ5Y2ZlYTY3ODIyY2Y2OTExMzIzNjNkNjhkNmYxZTE2OWY1ZTBkNGY3YzE0ZTVmYTFlNmQ0ZTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjZkYjY1MTcwZmY0OTU0NzZjMzZmMmRiMzU1YjRiNGQ3NTEzYzY0YmYxODc5MDMwZTY5Nzk4ZjliYmJkYmNlNzUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImEzMjg2OTE1NTAyZmNmOTZhNTg3ZjVmNjMwYzRjOTE2Mzg3NzU5M2FjYzEyYThkMTFiMjEzMzMzY2Y5MDdjNmIifQ" + } + ] + }, + { + "id": "claim_44cbe7e82cb84677b5dea7812f2cc936", + "statement": "save_config deep-merges through toml_edit — user comments and unmodelled keys survive, a corrupt existing file is treated as empty rather than blocking the write — and persists atomically via a 0600 .toml.tmp plus rename; load_config_at runs one-shot migrations (keybind migrations, retired-key cleanup) that persist through the same merge so each runs exactly once.", + "evidence": [ + { + "resource": "repo://src/config.rs#L332-L379", + "version": "repo-lines-v1:sha256:b0b5c8b2397a9966a793c2d484212d599dd84a417ea1b2e125b68ca493116b2c:eyJzZWxlY3RlZExpbmVDb3VudCI6NDgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImZjMjQyZjhlZDRhNWRhNWYwMDI0YjhmOGRhMGRjZWQ3NmY2MmI3ZTM3ZGY1ODJjOWQwNTkzMTY1MTg3NWQ1MTAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImY5MmQxZWIxMGUwYzM2Y2RmM2E4ZGJmOWRmMjNmMjZjNjU3ZjMyZWY5MWI1ZmVjY2I4YjA3YjViMzI0YzY4ODQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQ0ZTVkYTkwMzdjZTBhM2NhOTgyOWY3ZDc2NDFkYmY4ZDM1MWM3YzdmMTg0ZDgzYTNjNGE3OWVlOGZjNzUzNGQifQ" + }, + { + "resource": "repo://src/config.rs#L381-L415", + "version": "repo-lines-v1:sha256:2e864db0bba158c2f6def1a110477db760f4280ff66170ebbba2292c8c4469c5:eyJzZWxlY3RlZExpbmVDb3VudCI6MzUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImJjZjgyMWJkZWE1ZWM5NGI1ZGM5MmUxMGY1M2RkYjk2YjUxNGYxZTcxMDNlYTgxNTUwN2MyZjlkYTlmNTE3NDYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImViMmExMmFkNWVjZmQ3NzE2ZDU4ZTFlYjg4ZDAxOTI3Yzg5MDNiYzRkYTEzYWI5NTliM2M1YjQ1ZWVlODE2ZTMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjYwNjgyNDhkZTA0YmNlNGYxM2NkOTkxOTYzZWFhMjUwNjVhMWY3NzIxZTI3YTIyOTExMGEyYTI4ZGVkZjFhYWQifQ" + }, + { + "resource": "repo://src/config.rs#L476-L494", + "version": "repo-lines-v1:sha256:f188716f2da147d8bb335b0b8c02b816e13e1007611f3f10c98693c7b0a1f321:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk1Nzg1MmNlNGFiNzJlMDNiOTE3YmYwOWFmZjA1OGE1MDNlZDAxYmEwZGNkOWYxMWJlNzVhZTY3OWI1ZDIyM2QiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjM1ZjZmNDY2ZTU5Y2ZjMzA3MDNjNDVmNzJhZjY1ZGU1NGFjMzYzZDM1NGNjMWEzMzMwMzUwYWNkNDUyNDM0YzQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImMzYzliY2YzMzJkODc5NDQ4YTYyNjkwODQ3NDg4ZWFlMDIyMzAzZTQ3MTcwZDU4ZTE0YWE4ZWM0ZTE0MGY1NjcifQ" + } + ] + }, + { + "id": "claim_927c162521af497592f3efa9e2893cfe", + "statement": "App::config_target returns the profile's own config.toml and every config persist path (settings toggles, keybind editor, forms, theme picker Enter) funnels through save_config_to, which writes that target (the global file only in compat mode) — the 0.14.2 fix that stopped theme/settings writes landing in the global ~/.config/sshub/config.toml under profile mode, where startup never reads them.", + "evidence": [ + { + "resource": "repo://src/app/keys.rs#L1401-L1418", + "version": "repo-lines-v1:sha256:fe6f2ca10aa3126a40dd70d18e06d4ebb93cffd47a4f847e67b8cba4ff4ddf69:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImEzNGFiZTU2YzE2MzA3ZjUwZGM4N2VjYTAzNTE2NjkyNjdiNzY5MDRjMGI3MTFlZmE2MGFlNDYzZmE3ZjUxMzAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE2MmVlNTdlNDFhOGMyMGFlMDE2NGRmMjYxNDM5ZGNjNDk5ZDM1MjA4MzQxNTE1MTZkMDQ3ZThkZWFlOGE0NjYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjFkODRkMmQ0ODY3NmYxZmFkMmMwZGYwMjJhZGJjZjQxNGYyOGIyMDlkOGY4MzczNzdkMWFjZjY5ZTBiZWEwMzYifQ" + }, + { + "resource": "repo://src/app/keys.rs#L1447-L1458", + "version": "repo-lines-v1:sha256:7322fbf2d41e7032adad132dfe4f1ecad12d162dc698f92959ddeeda1de3edf1:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE0MzA3NjgzOTk1ZWQ1MmM3ZGM5MDg0Zjc2NWNmM2VmNzAxNTBmNjhjYWI4OTMyNDM3NmViNjI2OGQyMjQ2YmUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ" + }, + { + "resource": "repo://src/app/theme_picker.rs#L462-L473", + "version": "repo-lines-v1:sha256:4935253a86545c9309b7b587af1b8b100a5729a7a3c2dcb428ccaaea99ae3fc2:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjgxYjA4MDRjOTQzMDY3YTczNDZjZWY1N2YzMGZlZThjNzNiNDlmNGQ1YjNlZDczMmUzZjBmN2VjNzZjMWYxMmYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQ2YTRiZmRiNWE0NWU1YmQ1MzcxZDI2YWUwMDNiODc0MGY3MGY3OGYzMDU5NjEzZTczZTFlYjUxNzE5MGEyNDIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjUwNTE1N2ZlNDRhMjcwMGRmMmQ4MjJjNzIyODEzZjhlN2MzN2UwZGE4YTcxNDQzNGFmZGJjMjkxZTY3MzMwMzMifQ" + } + ] + }, + { + "id": "claim_6c414634b6c94dd8b8415c397a00248b", + "statement": "A profile's imported ssh_config source is resolved in order: SSHUB_SSH_CONFIG / SSH_LAUNCHER_SSH_CONFIG env overrides, then [ssh] config_path in the profile's config.toml (~ expanded), then ~/.ssh/config (profile::ssh_config_path_for_profile).", + "evidence": [ + { + "resource": "repo://src/config.rs#L292-L299", + "version": "repo-lines-v1:sha256:eb6b4e731763cb6493ead3afadd5cd7d67ac6299cdc3073c67476f19100a08a3:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYTFlMjZjNTAxMTM2MzczZDcxN2UzY2RmMzNhNDY5N2M1NmI3NTA5YTY0ZDliNTk0YWRiZDZjMDc0NWZkNjJlNyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMTM1M2FmYTRlZjllNWUzM2JiYjE2Yzg2YmI0NDJjZWYzNGQ2OWJlYTQ3MWFjOTk1NmRlMmYwMzFkNTkzNzBjMiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZDI4YTkwY2E1MWI3MzlkMWQwMTViMDYzNTc2YmJlOTliYTA2NGE3YzYwMDk2NmEyNmI0ZjEyMTlhYjQ3Yzg2MCJ9" + }, + { + "resource": "repo://src/profile/mod.rs#L475-L499", + "version": "repo-lines-v1:sha256:a97135fabc70491aab0e051b2648257c7cc7ef5adfd8831d78116c54a9318472:eyJzZWxlY3RlZExpbmVDb3VudCI6MjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImM5ODc3MzVkY2RkNDUxMjlmYjgwNmNmNzMyYTJkOTE0ODUxMTdjNWVhZDMyNDgyNzMyNzQ2NTU1ODE3Mzc5ZGQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImFkODA3MDNjOTU3OTI5YTQzZmZiMzRkOTliMjQxZGM5ZDYxYzNkNjE5YjhmNDdkODZmNTQxNzI2YjFjNDg4NGUifQ" + } + ] + }, + { + "id": "claim_fc935601885f4ffcad29aa1aedff2c71", + "statement": "User themes are *.toml files in a themes/ directory beside the profile's config.toml (App::new_with_profile) — compat mode and the headless theme CLI use ~/.config/sshub/themes (or $SSHUB_CONFIG_DIR/themes, resolved read-only via config_dir_path so listing never creates or migrates anything). A theme's id is its file stem, built-in ids are reserved and never shadowable, the registry caps files at 1 MiB and 256 files, and discovery is one level deep.", + "evidence": [ + { + "resource": "repo://src/app/mod.rs#L807-L819", + "version": "repo-lines-v1:sha256:08b3eb3347da5f1e43f1015ac318bb2780d61f356c6acbf0f5de2a76e4716fa5:eyJzZWxlY3RlZExpbmVDb3VudCI6MTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM3MjU5Y2ExZDJhOTliYTI2MDNiZjNhNzY5N2VhMzI1Y2QwYjk5YzhlNWJkMzhjYjU0MzI3MmE1MmFjMDIwZmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjZjOTE5MGY5YWU5NzkzMWUxZjkyMjZjMDRlMWY3MTVjZDVlYTdhMjk4ZWE3ODlhMTcyZTE5YWE1NmIzMzc1MGYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjY2ODg5Y2NhZjk2NDBiMDhjZDc3YmJhNzFkMjRjMTQ1YmQ4OTQ4ZjhhNTJhYjY1NDhiMGU3Zjk0YjZiZjRkNjYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImY5MzI3ZjM3NjJhNjJlNWM3ODM5NjE5ZjhmYTllOWQxMjRjYWI2OGVlZTUyNWZmODljMTA1YmY4NzlhMGMxMjQifQ" + }, + { + "resource": "repo://src/cli/theme.rs#L71-L79", + "version": "repo-lines-v1:sha256:1ac515f618cfd8c964f074e85c2f4231cd22a94d439e9ddbdf6cabcea887c33c:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiOTExZThiNjJlMzNiYzQ4NDFjODZhNTJiYmZkNDI0YWU0MWUwYmJlYTgyNDIyZDEwZTBkMjBiOWYzMDVmYjQwYyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZmE0ZjYzOTI3MGI4ZTlmNGU2MTIxYTIzNTQyZDdkMzYxYmMwODc0YTQ2YmZlMWJmOTkzZGFlMTBkMTEwOTA1ZCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMDgwYTEyMWRlODFiMDcyMjNhNGQzNDlkNWJlNzlkOGY4NDU2Y2Q0MGFiMWY1MTE1NjU2YWZjMjYyYjlhYzNlZiJ9" + }, + { + "resource": "repo://src/config.rs#L516-L529", + "version": "repo-lines-v1:sha256:02e655dcae8ecbf3aa8f7cfb9c7b9033b631be85cd1c6d7cf7175613da87cd31:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ1YTE4MGEyN2NmMjhkNzA4NzZjODlkYzBlODQ0MWE5ZGFjN2EwNWI1NGY2M2Y5YTk4NjQwZGRhYTViMWI5ZTAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImU0YjM0MjI2OTIyYjQ4Y2Y3YmZjMzdiMGI3ZjgxNWMzNjRlYmNjMmQ2ZDcxMDE1MTg5OTkzMTdhZWJiZjBjMDMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjdmNzdjMzgwMGUxZjAwZTFjZTljYTQ4NDVlMjMzNjU0OWZhMWZmYmUzYjcyYWYyY2FiZDBhZWQyZmUyZDYyZmEifQ" + }, + { + "resource": "repo://src/theme/registry.rs#L1-L13", + "version": "repo-lines-v1:sha256:b4ab922579e1fca1ecc2b4fee81fe02b46f84fa321e8e85b1107cd939ac781be:eyJzZWxlY3RlZExpbmVDb3VudCI6MTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk2ZjVhZjc5ZDY3NjRjNzkzNDg3NTFmMDI2NDM0Y2M5N2Y3MTZjNjk0YzgyODBlNmNjNDU3NWQ1NjhiOWQzNjQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM2MzYyNjM1YWVlOWM4NDEwNTYzYjBhNmRiYTFkYjcyZjczY2ZlNjQ2YjI3ZWEzYjk0ZDNkYjM4NDY3YTczNzkiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjA2MmFkMWU5MDUyZjM5NGU5ZjJiNjNlZGFiMmUwNTQ5OTgwNjUzYjJmZDI5ODYwZWQ2MzhkMGFmMWQ4MWNiNzcifQ" + }, + { + "resource": "repo://src/theme/registry.rs#L30-L40", + "version": "repo-lines-v1:sha256:702a89d3a69d09fdfc881b6c17301551c925745f87f310d20c1d297273c7fe08:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImIwMDdlYTRkZDk5YzQzODczZDU4YTk0OGExNzdmMWUzODExMmI1N2ZlZjk5NTZlNGRjMWViYWQ1MGE3ZGI1YTIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU5YTc3MjQ5ZmY2MzVkOTkyMDcxMjJiYWMxYmRmMWQ5OTZhOTIzN2Y5OWVjYTIzMzJmMmQwNzliNDYwYzQzN2YiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjJjZDA2YzA4Mjk0MTg5ODViMDM4NTJhZjYzMmI2MzY0ZTFjZTc0YjMzMjhmYTMxNTdkN2UyMDk3ODlhZDJlZGMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjlhZDVkMDBjYzBjZDQ3ODk0NjZhNGJjYTk4ODM3OWYwZjdhN2RiOTBjZjIwYzZjNTVlNmU3Zjc1OTc1OGJmOGMifQ" + } + ] + }, + { + "id": "claim_b7274f8a2631453bb577681f85772cae", + "statement": "Theme loading is deliberately infallible: App::load_themes_from degrades an unreadable themes directory to the embedded built-ins with a non-fatal notice (keeping the failed directory for reload) so a broken themes dir never blocks startup, and a missing or invalid appearance.active_theme falls back to the embedded `default` while saved_id preserves the configured value and config.toml is never rewritten.", + "evidence": [ + { + "resource": "repo://src/app/mod.rs#L752-L791", + "version": "repo-lines-v1:sha256:792635eee70182c05af6f97bbdb5cf6ebc19314acc78a76da3e6f154b5d27710:eyJzZWxlY3RlZExpbmVDb3VudCI6NDAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijc3YzJlNWYwNmYyMmEzMDIwNWE2NTAwNWM3YmE5NzVjZDhmZmIxMzkwZWE3ZWIxYmVjNTUxYjU1OGUzMTQzOTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjczYzNlMjNmOWYxZDZjZmY5MWYzOTBkY2M4YzdkOTU5MTAzZThkMjVlMzEwNWE3N2I4OTQzMzAzODBjNWQ2ZTYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjdlMzhmOTI1OWUzYTNmNzc0YjY1NzEyMWVkYTViOWUxZGY1ZTYxYTQ4ODBmZDMwNDJjMTk5YzM5YjUzM2UzN2QifQ" + }, + { + "resource": "repo://src/theme/manager.rs#L290-L304", + "version": "repo-lines-v1:sha256:5c8b44c7611cd8cbd500aef75c8f09c6fd8562bb853aba4c83941af7f956f126:eyJzZWxlY3RlZExpbmVDb3VudCI6MTUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkyYjVlZGY1OTQ5Y2ZlYTY3ODIyY2Y2OTExMzIzNjNkNjhkNmYxZTE2OWY1ZTBkNGY3YzE0ZTVmYTFlNmQ0ZTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRkZWM0OGZjYzk3ZDVjODViZjViYzI4ZDU0MjQ4ZjE5M2UwZDBjZWRjMGE4ZTI4ODBjZjVhMWRkNTczYTkzNmIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjU4NTY2YWUwZTNkOWMxN2VhM2UzOWQwNmU4NTVhMzJjYTI1YmYzOTRlYTRmYzczM2Q0ZjQ2OWMyODQyOWZmYzMifQ" + }, + { + "resource": "repo://src/theme/manager.rs#L68-L114", + "version": "repo-lines-v1:sha256:ca1e0ac0711a35fdd9a0f2fe3be15aa7d6e67babb4b823075199d6761be7022f:eyJzZWxlY3RlZExpbmVDb3VudCI6NDcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijc2ZTkwZWYyOWQ5MDU2NjExYmE5ODA4ZGUwOTE2ZGU2ODg1ZmQ0ZTg4YjQ4OWQ1NTBlYzk1MWNlMDA2NjRlYzkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImM1Y2RlZjgwODVhZTc0YWI2MzZkNjQ1OWY1YzZkNWZiZmIwZGIyNjRmMWIwZDc1YmFjNTE5YzliM2Y2NzE3YWMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImU5YjU1NmQ0ZjU2YzRmZWFkN2U2YjRiZDg2ZDAyMWMwNGY5M2UwNTljMjg4ZmYyNzA3N2IxZGEzNGQ3NGRiYmIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImM1YzYyZDUzNzdiN2IxNWMxOWE0NzU5MjNhZWQ2MTcxZDNiZjAxNjAyNGVjNDM5OTA2ZDliM2NhMDI1NzFjNzEifQ" + } + ] + }, + { + "id": "claim_dc79d28ddd904aa7985e9120357fc15a", + "statement": "ProfilePaths is resolved once at startup and passed to every subsystem (they never rediscover paths from the environment); profiles retain stable ids for credential namespacing; state.toml is persisted atomically (0600 tmp + rename); and SSHUB_DATA_DIR/SSHUB_CONFIG_DIR (or SSH_LAUNCHER_* fallbacks) switch to compat mode that uses the override directories verbatim with no profiles/ nesting, no state.toml, and an empty credential prefix.", + "evidence": [ + { + "resource": "repo://src/profile/mod.rs#L1-L21", + "version": "repo-lines-v1:sha256:6c3aa156cd3c6a9936c2719a156b44effe993af0e243e6eb3065b9ad9d19b4fb:eyJzZWxlY3RlZExpbmVDb3VudCI6MjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImYyNWMxMmUyMTVjNmJlODZkYTM3YWRiOGI0ODU5MDI2OWUyNjU2Yjg3OGZkMWFhM2RmMjQ4OTlkNDZlNWEyNTAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk4NmU3ZTMzMDBlOTYxZGU1ZTBhN2YyM2FjYmVkODA2Yjk1YmEyODFjMGExZDNhZDMwOTgyNDM5NjczOThlZTYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQ3Y2VlOTlhMjQ4ZmM2N2E4YWUxMTcyODBiMGQzYTM0Mzc3ZmYyZGIzMjFiNDdmODQ3MWIwYTdmNmQ4N2M4YzkifQ" + }, + { + "resource": "repo://src/profile/mod.rs#L238-L313", + "version": "repo-lines-v1:sha256:e38ac6e2ccc945784f91e39a68c701f17481205f0f035211f2c90844672b8e5c:eyJzZWxlY3RlZExpbmVDb3VudCI6NzYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI4NjdiMTEyMzg0MGZiNzhjNTMxNWYwZmM5NGExZWMwZmRlMTc5NzY3N2M4YzI0YmE5Njc0MzUxODM2YmRjMTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjNlYTE3ZWYwOWNjY2Y5ODZmNWE3YjIwZTA4Y2RlYTU2ZWIzZGNmMTg4NWE2NTlkY2MzOWE1NzZmOTdiMGU4NGUifQ" + }, + { + "resource": "repo://src/profile/mod.rs#L94-L105", + "version": "repo-lines-v1:sha256:a9dc488e9be58eea4521f51aaf2ab80079526e6c7071694bd3b1a642d4c490b1:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjhiYWVlYTkyNTVhNGY1NjNjNzk5MWM1MjY0MDM3ZmUyMjZiZDZhZWQ2MDJlMWQwNDJhYjUwMzM2ZjU2NTQ4OTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImIyNDFiMzk0YTc2NjM5YWFjZTRlODYwOWI0OWJjNjJkNWVmY2EzZjExNGM3N2ZhMDAyOTQ1NjEwNmY2NmQyMDUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjAyZTA1NzUwMzhmM2I1NGQ2M2I5ODAyYjFjZjUwOTUzN2YwMzdlNWE1ZThiYzNjYjJmNjA0YTRjZjI4MjE3YTgifQ" + } + ] + }, + { + "id": "claim_a74678c9118a4f869b81d0a93e0b746f", + "statement": "LauncherStore wraps a single Mutex; opening it creates the parent directory, restricts the DB file and sidecars to owner-only, enables PRAGMA foreign_keys=ON and busy_timeout=5000, runs migrations, and seeds a \"Default\" identity.", + "evidence": [ + { + "resource": "repo://src/store/identities.rs#L9-L27", + "version": "repo-lines-v1:sha256:fef429f4f8aa2b7dbd7231e3f25f735aec0a271914837ecfc772d294c6c01275:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImMyMzU4NjVlNTBmYTZjYTM4NjUxM2E1MTM5NjNhMDNmYjMzYjUzN2QyMzQwNzI1ZDhlN2EyNjZlZTBlZDMxYWUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImNjMzU5NmU5MThiZWM2Y2NjYTE0MzcwMDMzODQxZTRjMjYwYzg5YTA2ZmMxMjViZDk0MjAxY2FmNzg0ODIxNTgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhiY2ZiYWFlNzdlYjIxYzJhZTJkYWU4Mjk0NDNkYmMwNjRiNzU3ZWYyMzEwNDAzY2VmMDBhNWUzMGNkYjA3M2QifQ" + }, + { + "resource": "repo://src/store/migrate.rs#L70-L74", + "version": "repo-lines-v1:sha256:c8dd6ea052e77277ced8e56134d1eaae08038416f8710b15a09360c65cb19d67:eyJzZWxlY3RlZExpbmVDb3VudCI6NSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNzIxMWRiYjk1NjVmZDc5Y2JhZWJmNWM3NTRmOGQyMzk1MTczNmVjMzY3OTRmMzgzZTIxOWYyYWM2OWU0ZTAyYiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNzk0MjViMjg2YmQxYjk2ZDMwYTA4MTIzN2ZmMmE2ZDA4Yjc2MDY0MWQxNTkzODg3ZWZjMDU3NWYxM2RkMGJhMCIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYjk0ZWM1OWNmZDA0MjBkODQ0NDliMWQ5Mjk4MTMxNGRlYjhlN2E2OWQyNWVkMDAxN2U5YWJmODI0MmJiNTIxYiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMDRjM2ViMGY2NTk2MmM0MjUwYWUwNGJiYTc1ZDhjMmQ4MDYxZmY0Mjc3NWFmMWVmYmFhZTQ2ZGMyOTM0ZjgyZSJ9" + }, + { + "resource": "repo://src/store/mod.rs#L26-L52", + "version": "repo-lines-v1:sha256:ba0bc3cc5e7366cf1aefae2dfb4e1908ce92891253e0418ad324485ee33a5b5b:eyJzZWxlY3RlZExpbmVDb3VudCI6MjcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjdkNjFmMTQ3NmQ5NjlmZTI5ZTEyNjliNGEwMDllNmVhZjFhNjAzYmQ5N2YwZTAyZGNmOTE0MTU1OTQ4YTU4MzkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijc4MzhkODAxM2NlYzBhNjU1ODU1ZTQxNjViOTRkMzI5MjEzODMzNzE4YzBhMjZmMGI0NzYwYzZlODRiN2ViNzIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhhNzY5MTA1NDM4ZjQ5NGExMjQxMjkzMDU1NWYzNmNlOTA5YzcyNTUyNWQ4ZDQwYTJhOTUxOGYzMzhiMjg4NjUifQ" + } + ] + } + ], + "verification": { + "by": "openwiki/0.5.1", + "at": "2026-09-11T12:46:43.817Z" + } +} diff --git a/openwiki/.claims/architecture/overview.json b/openwiki/.claims/architecture/overview.json new file mode 100644 index 00000000..ae44ae39 --- /dev/null +++ b/openwiki/.claims/architecture/overview.json @@ -0,0 +1,636 @@ +{ + "schemaVersion": 1, + "pageVersion": "sha256:cbec095865f03901034a81626fe09be505f6c6746c16a03a3315723e57f4edd7", + "claims": [ + { + "id": "claim_d0f16c365d73470fac8b2da24afe2a1e", + "statement": "SSHub has no async runtime: startup resolves one immutable profile workspace (optionally via the standalone picker), builds App, and everything afterwards is driven by a single synchronous event loop in src/lib.rs (run() -> run_with() -> run_app_with() -> run_terminal_loop()), with concurrency handled by background threads communicating over std::sync::mpsc.", + "evidence": [ + { + "resource": "repo://src/lib.rs#L271-L290", + "version": "repo-lines-v1:sha256:ed34afe99e7dce033efd6a1c8315a8c1d88e2c825d93e2418086c6a5363d93c7:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4MTc1ZGM5YmI4ZmFlOTlmOTdkNzZkYzEyMzg5MzdjMGQ0NjZjOTk5Mjk4ZjlmMmMyZjA0OGU0ZmJiMmNkN2QiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImUzOTMyOGUwMDM3N2JiYWU3Y2Q4YjRiOGI1OTljYmRjOGFjZmNlMmQ5N2QzYzA3NmRkMzY3NWRlZjdmOGFlNTYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjM1ZjZmNDY2ZTU5Y2ZjMzA3MDNjNDVmNzJhZjY1ZGU1NGFjMzYzZDM1NGNjMWEzMzMwMzUwYWNkNDUyNDM0YzQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhhMTBkODc2ZDQxY2EwZjRlMGMwMTJkYzQ2OWQyN2M3OGMzZTI2NTA2ZmZjYTBhOTg2NjcwOGJjMmI0MmRlYjgifQ" + }, + { + "resource": "repo://src/lib.rs#L96-L166", + "version": "repo-lines-v1:sha256:e92a43b55ddbf4a017731b201226f1f8d9bdc74fc81788b6a72af1681797c3a1:eyJzZWxlY3RlZExpbmVDb3VudCI6NzEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjIzYzViYmVlYjFhNTE4Y2Y3NjgxYWQ5ZmFkOGJiMjU2ZmNiZjFjZTc5Y2Q0ZDg5YTlhMDM0NjMyZDViY2MzYzMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRiNzUzNzgzZTQ3ZjJmMThlZWY3MTYyYjg3YjEzZjYwNDNlMWIzNTI1ZGUzNTMwMDJlOTRkMzU0MTQ4ZGQ3NDkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjA3YTE5YmRhMDA3OWFkMWJjMTY1NjNiYzhiMTM0ODliOWFkODE0NGNiNTc4OWQ0Y2FjYzI5MjNkNzhmMDkxNjIifQ" + } + ] + }, + { + "id": "claim_4bd0f3140a214847b6825ed4a4be3f90", + "statement": "Each iteration of run_terminal_loop (POLL_INTERVAL = 50 ms) reads the terminal size, drains every open session's PTY (feeding the vt100 parser, session log, and stderr debug tail) and resizes all sessions when the host terminal changed size, relays OSC 52 clipboard writes for the visible session only, promotes app.mode from Connecting to Session once the active session's phase is Running, draws via terminal.draw(|frame| tui::render(frame, app)), then runs poll_keys_and_watcher, and exits via shutdown_all when should_quit is set.", + "evidence": [ + { + "resource": "repo://src/lib.rs#L292-L411", + "version": "repo-lines-v1:sha256:99533e546119f652e28e599200288e0aeb230081eae7eb67d60d03f0ff1e1b4f:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIwLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiJiOTRjYWE2MWQ0Zjk2NmVkNjg0NzVjNzk0OGZmNjZjNWNlN2QzMGRkMDczZDU3YjY3ODIzNDRjOTdkN2I5ZGY3IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0ODkzZjQwZTM3ODFlYjg5NmM4MmQwNDZhMDgwNjdhMTFlMzVjZGYyZjFkZmFmMjZmZWE5MGI4ZGI2Njc2NDEzIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJmNmUzNWQwMzUzYTc2ZDIwMDVmZTNjZGIyNTIyZTQwODljZmU2YjgxY2I4OTQ5N2Q4MzNiNmE5ZGM3ZDJjMWI2IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiIxMWZiMzZmMDQ2ZDQxZWJhMWYzMjM0Yzk4NDJhYjRiMmQzNDc1MjZkMTY0Nzk2OGI1ODVlOTBjMTE4ZWFlNWRhIn0" + } + ] + }, + { + "id": "claim_e6d7a367831b46beb3a790a493188909", + "statement": "poll_keys_and_watcher uses a 50 ms idle poll window but shortens it to 16 ms while App::animating() reports any in-flight effect, and when a crossterm event is ready it drains ALL queued events per frame (Key -> handle_key, Mouse -> handle_mouse, Paste -> handle_paste) because one event per 50 ms frame would make pasting into an embedded session crawl at ~20 chars/sec.", + "evidence": [ + { + "resource": "repo://src/lib.rs#L450-L472", + "version": "repo-lines-v1:sha256:d9b3e516a111e1f5e18ad25c1b34b610dbcd243efa547bc474894e86c1bb3bfe:eyJzZWxlY3RlZExpbmVDb3VudCI6MjMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk4NDcxYTJiNjI5ZjA4YzBlOTdlZmY3YTE3Mjc4MDVjNTkyNzY2MDU3YjYyMjIwNDEyN2RjY2EwMGU4Y2ExYjIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjM1ZjZmNDY2ZTU5Y2ZjMzA3MDNjNDVmNzJhZjY1ZGU1NGFjMzYzZDM1NGNjMWEzMzMwMzUwYWNkNDUyNDM0YzQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImY3ZDk0Y2VmNjNhNGI0ODc1NGI2YjlkN2MxNmU0ZDI3OGM2NGRjN2MwY2Q4MzM4ODA5NDA1ZGI5MWY3MGU2ZjcifQ" + } + ] + }, + { + "id": "claim_0ec0ecfcc5b84090b650d6365087c576", + "statement": "After input, poll_keys_and_watcher drains every worker channel non-blockingly in a fixed order: config watcher events collapse into one app.reload_hosts(); ping results fold into a per-host ring capped at 30 samples (PING_UNREACHABLE = u32::MAX as sentinel, cleared when the host recovers); SFTP events are collected first then applied via apply_sftp_event / apply_sftp_event_left (the borrow must end before applying); OS-detect events are likewise collected then applied via apply_os_detect, which persists the os_icon and reloads hosts.", + "evidence": [ + { + "resource": "repo://src/lib.rs#L474-L544", + "version": "repo-lines-v1:sha256:616729a6fe002274949aef9c41711b5cf36be45e42d7f098a76a3bcbd17a24ca:eyJzZWxlY3RlZExpbmVDb3VudCI6NzEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE2MGRlNmQwMDE4Y2FjNjZlODk0ZTFlMWQyNmNhN2MzZDc1ODJhOGRjYzk4YTM2YTU4ZWM2Zjc5YmIxZjNiOTciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImVhNjNiZjc2MTY4Mjg3ZWExM2VmYWFiYzdkYTVkZmI4MzUyZGNiNmExMjk5ZjU4YjM3NzE1MWEwMjdhZGY5MWIifQ" + } + ] + }, + { + "id": "claim_79eeeb2e9c7b4dc0b869a1c97aa34dac", + "statement": "The probe_rx drain in poll_keys_and_watcher is a retired seam: nothing ever sets probe_rx (the 60-second `ssh -v` prober was removed because it buried user-relevant events under hundreds of probe lines), so the SSH log is fed only by user-initiated events; clearing the SSH log drops the receiver.", + "evidence": [ + { + "resource": "repo://src/app/keys.rs#L346-L351", + "version": "repo-lines-v1:sha256:62a4c48f80c6e898d257021191c6d77566600eb18fe53ee995d52d010d4752c5:eyJzZWxlY3RlZExpbmVDb3VudCI6NiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZTZhYWIwOWExOTYxZWQ5YjZhNTMzOGY2ZTRkNzI4YWM1NDVmNTVjMzdlZDRmNWQzZmFiNGJlOWMyY2ZlMThmZiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNTUyMmM0MmNkMTljNmQwM2Q4MDhjNDUyNzIwNTUyODI1YjdlYTFjNWI0OTFiMjE4NTExY2FjNzQxNWNmOWQ5YiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMjdkODJlZTRhNDFhZjQ1NDNiZTQ5ZjBlNDIwY2FiNGJlYWNlY2U0NDgyMDE5NzAyOTNhYThlN2FiM2EwYjk2NiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNTA5NjAwYjFjODJlOTY4ODg2YmFhZjc1ODlhNWJiOWUyZTZjMzhmMjRhYWJlYzAyNDQ5Y2QxYWQwZjE0NGQ1ZSJ9" + }, + { + "resource": "repo://src/app/mod.rs#L1124-L1129", + "version": "repo-lines-v1:sha256:61e031c7fac95986cc5de2d84c4aab3f018fb10d2d181def58e3f24ec855ef36:eyJzZWxlY3RlZExpbmVDb3VudCI6NiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNmJiZDU2NTU4OWY1N2Q3OGJhNWNkNWRlZDYyNzgyYzdiODhmMzNhNmExNzlkY2ExYWY5NmUzYWNlNDg5ODFmYSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZmZkZWFiOWJkZGM3Nzg2YmU0NmExMzAyMzEzNDliZmExNTdiYTYzZjc4Y2IwY2YxNTE5NDdhMjQzYWI4Nzk3ZCIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiOGRjYTM5NTJlMjQxMmYwYjNiYTU0Y2JjN2UyMGIyMzFkNDMwMGZmMjIyNGVmNWZhMTE3NDc2NTczZmVhYmI1NiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiOTk5YmRlZmVlYWNhNjM0MGUwMjkwZGM4NmU4ODljODlkNWUxODlhYTViYTQ0YjU4NWIzYjcwNzM0ZjRhNjJkOCJ9" + }, + { + "resource": "repo://src/app/mod.rs#L449-L449", + "version": "repo-lines-v1:sha256:a66e4ac0987071d2c7cec85a6a89e130e3419c0b1103faae2629c958bc22081e:eyJzZWxlY3RlZExpbmVDb3VudCI6MSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYTY2ZTRhYzA5ODcwNzFkMmM3Y2VjODVhNmE4OWUxMzBlMzQxOWMwYjExMDNmYWFlMjYyOWM5NThiYzIyMDgxZSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYTY2ZTRhYzA5ODcwNzFkMmM3Y2VjODVhNmE4OWUxMzBlMzQxOWMwYjExMDNmYWFlMjYyOWM5NThiYzIyMDgxZSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiOGM1MGI5YjJjNjE2NmM5ZTI0YTViOTljOGRlOTU5ZmFiNTgzN2Q5ZjI0ZTBjYjQ3NTcxNjZiYWZjOTE2YzlhOCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNzJhNGE2MjJjNzA0OTM5YmUwNWJiYWYwNjE5ZGZkZjQxYWQ1YWI5NDQ5ZWRhYWE0NzRjYTZmNmIyZjBmZTY0MSJ9" + }, + { + "resource": "repo://src/lib.rs#L528-L534", + "version": "repo-lines-v1:sha256:4e57f51526a233618b3ae6ea989cb91da07f5f9e6709f1c0798e726edaa994c5:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZDMzYWIwNzFmOGQ4ZDkyMjkxNGIxZTI2ODgyMmYwZTEzM2NlYTMwOGY0YWY0ZTQyYWE4Y2YxNjJmYzQ2ZmVjZiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiOTk5YmRlZmVlYWNhNjM0MGUwMjkwZGM4NmU4ODljODlkNWUxODlhYTViYTQ0YjU4NWIzYjcwNzM0ZjRhNjJkOCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiYTczNjg3MTQ4ZjcxMmJjOTQ3OGU0ZDAwMzBiMWFkYzI0ZDg5M2EwMTRmYjU1YjUxZWMwYjVkOWE0Y2U3MmY0OCJ9" + } + ] + }, + { + "id": "claim_64a88be79f264ce08be01962bf019015", + "statement": "tick_broadcast() runs every frame: it expires finished error toasts even with no live run, drains the broadcast worker channel and folds events into the result table, spawns a toast per failed/non-zero-exit host, arms the settle countdown when every row is terminal, and writes the audit trail exactly once guarded by audit_written.", + "evidence": [ + { + "resource": "repo://src/app/broadcast.rs#L495-L590", + "version": "repo-lines-v1:sha256:b5f07bc1d7d878e631f5daf6cb1c0532559ca974c51085c4fd150d30157308aa:eyJzZWxlY3RlZExpbmVDb3VudCI6OTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNmZWI0MTZkZGEyNjBiODI5N2E5NjRhZDc5NGYxMTdkODg3NGJkOTQ4ZGM0MTE3YjJjZjQ3MDRjYWE5OGVmOGIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImMwNDMyZGE1NDYxNjNhMzZmN2ZiOWNjMjM1ZmUzMzU0YTE4NzQ1MjhiYzliNjUwNmYzNTZlMWNiMjY1ODYxNzAiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjY1Yzc3YTZhYjU5ZmViZGM0YzlmNzA5MTRmNzg0MzllYTRhMmFmMTdhMWViNjFjZWI1MDhmMmE0OThiMzBkODciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjM1MTBkMDhlODM1MTg4ODUwYmUwNWFhM2QzZmU3Y2EzNTE5YjgyYzg1MzQzOGYxZGYyNGFiYThiMjcyMzIwZTkifQ" + } + ] + }, + { + "id": "claim_5a9f32bd91644a2db94e88877c951fa3", + "statement": "tick_tunnels() runs every frame and performs the one-shot keep-alive auto-connect bootstrap, TunnelManager::check_health via try_wait (stabilizing spawns after stable_secs), reconnect scheduling with audit logging, and a 2-second resync (tunnels_synced) that re-reads the tunnel list from the SQLite store because another sshub window or the CLI writes the same file.", + "evidence": [ + { + "resource": "repo://src/app/tunnels.rs#L652-L668", + "version": "repo-lines-v1:sha256:81c08929bdef61b07eacbc3dac23a5f962ff0498a984d9e9becd5264ff188130:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNhMTVmZjcxN2QyZmE0ZDBmZWM1ODIwNGFlNjg1MDdiOGNiZDdiNzFkN2JmODA3Yjg3MDAzNjUxNjFkNjkyMjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE2MmVlNTdlNDFhOGMyMGFlMDE2NGRmMjYxNDM5ZGNjNDk5ZDM1MjA4MzQxNTE1MTZkMDQ3ZThkZWFlOGE0NjYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImZmZTg1MzVkZmNlM2RjMTE3ODA1YzMzZjY2YmFhYTFkMmI0NzljZjBlOTRhYTY1ZjBjYmU5MjNmNDgxZjFmYjMifQ" + }, + { + "resource": "repo://src/tunnel/mod.rs#L220-L281", + "version": "repo-lines-v1:sha256:1123dd95c634323a20eeaaeca4d058047e2d4595918dbb613689c3994546aebb:eyJzZWxlY3RlZExpbmVDb3VudCI6NjIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijc2NzVjM2ZhMjlmOTY3NGQ3ODE0NTE3MmU3NmQyMGZjYTI5NTgzYjVhMmZmN2VlMDUwMGQyNGMwNDE1NTI3NTEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRmMWViNjg5NTgyZWI3OTY3NWE3NzE1MzAwZDA2ZThlOWRmNDE3MjIxY2U1ZmUwZmJjMDA5OTJmZTcwYmYwZWYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjljYmRmY2JkYTVjNTJlMWI4OTAwYTY4ZjFlOGMxN2E0MjlmODg4MTZhOGE3YjhmNDE3MjYyZjlhYTc1NGViYjgifQ" + } + ] + }, + { + "id": "claim_f1b5a1575ac846e5888a6e97780419e7", + "statement": "refresh_auth_cache() refreshes the audit events cache every 10 seconds, re-querying with the audit tab's current status filter and range so the periodic refresh preserves the user's filtered view instead of clobbering it.", + "evidence": [ + { + "resource": "repo://src/app/mod.rs#L1074-L1092", + "version": "repo-lines-v1:sha256:cee4396d4be7ac365519d280fc5a461a66b0ddaf3d65347092d0389799347fb2:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk2NTgwZGY3MWE0NmJiMzU2N2NkODMwYzZjMjViNGE0ZGFhMjA2NTU1ZTM3MzVjZTZjOTQyMTk5MmY2ZjVkZjAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImMzYzU4YzU5OThmODQ3NTg1YmJkYzg1ZDIyYmQ4NWFiNDVjY2E2MjU1NTc3MjQxOGJkMmMxMGFlNGFlNmI5MTgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjZkNjVhOTg3NzY0NzRjN2E2YWI2ODhjZGVlMDI5MzQyNTkwMmQ5YjQwYWMwNzcyOWEyMWYwYTlkNzdlMjBiZWYifQ" + } + ] + }, + { + "id": "claim_eab18a3cd4e6469b958554845fdfaded", + "statement": "detect_tab_switch() is deliberately the last step of poll_keys_and_watcher: it must run after the background drains so a mode change arriving from a worker event (e.g. an SFTP ConnectFailed) stamps mode_entered_at in the same tick; it compares per-tick mirrors (anim_prev_tab, anim_prev_mode, anim_prev_audit, and siblings) against current state to arm tab-switch/popup/session slides and to retire finished ones, freeing their snapshot buffers.", + "evidence": [ + { + "resource": "repo://src/app/mod.rs#L539-L651", + "version": "repo-lines-v1:sha256:20809b84210c8a0cfa5492f50f5c921ad0b8091b54b16865f64a0d332555d6e5:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEzLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiIwZDk3YzE2MjBiOTllNjBmM2JmOWU2ZWVlNDM4ZDE5MDQ0NzI3MDNiNzhhM2U5NjAzMWE2ZmMwMDIzNWIzODRkIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0ODkzZjQwZTM3ODFlYjg5NmM4MmQwNDZhMDgwNjdhMTFlMzVjZGYyZjFkZmFmMjZmZWE5MGI4ZGI2Njc2NDEzIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI5OTliZGVmZWVhY2E2MzQwZTAyOTBkYzg2ZTg4OWM4OWQ1ZTE4OWFhNWJhNDRiNTg1YjNiNzA3MzRmNGE2MmQ4IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiIyZjg4YmMxZGVlMjE3MjdjYjU5MjU3MGU0MWQ4MWRkMmIwZTg4Mjc0N2ExNDViMjFkNTBiOTRiNDY1ZTE0OGYxIn0" + }, + { + "resource": "repo://src/lib.rs#L561-L568", + "version": "repo-lines-v1:sha256:0d00b7b1e9d69fa5709136b82e5dff9937518654b6750d71324dc8ad466d2c1f:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZjZkZmZmZmQ2OGVhMWVkNWI5ZTJmNjNhNjE2MzM0ZDQwYmRhYmZlZjRkODg4MzZkZTA5NmU2YmI0YWUwMGQ2MSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZDg5NzcxZmUxYjY2Mjg4NzVhYTIzZTdkYmU0YWE3MTQ2MWQwMGNkZmI2NmRkZjExYjlkODIwYWM1ODk3ZmRmNiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMmIyZmI4MGJiZmI2ZDI0MTc4NWFlOWE3MDJmMWI0YTg2MzAyYjE2OTM1ZjEzMWIwOGI0OWY1M2FiNTIyYjUyYyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMzkwNjUxMjU2MDM3NDE1ZjlkZWE3ZWY4YzIxYjA0MWI0Y2EzZDEzNjFiYTBjMmQ2ZmRlY2FhNDNiM2I0YjczYSJ9" + } + ] + }, + { + "id": "claim_e4d22a7546ef4424933df7563db59a96", + "statement": "When stdout is not a terminal, run_headless_loop renders one frame on a ratatui TestBackend (80x24) and then either applies SSHUB_AUTO_QUIT or bails with 'sshub requires an interactive terminal'; SSHUB_AUTO_QUIT=q sends the q key and confirms the quit dialog if it appeared, failing the run if should_quit was not set. SSHUB_DRY_RUN (or legacy SSH_LAUNCHER_DRY_RUN) short-circuits run_with() to Ok(()), and `--dry-run` is handled in src/main.rs before the TUI is entered; tests/smoke/run_app_quit.rs drives exactly this path.", + "evidence": [ + { + "resource": "repo://src/lib.rs#L101-L106", + "version": "repo-lines-v1:sha256:58d514dbefc5f4dd557f216b1adf26b802ddcd93fd7ecb1318be6a9bdface60e:eyJzZWxlY3RlZExpbmVDb3VudCI6NiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMmViNGZmNTRkMDdmN2IzYzY3NWQ3NDE4YTQ3MmYxNjBkMTUzYzRkMGRmMzhlMGE2NmYzYjk4YjA5ZTdjNGIxMCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMWI4MjhhZWJhNzI3YTk2ZDAwNDE5OWZmODRiYTA3MDc2MGNmNjUxMzViYzA4MGYwM2Q5Nzk4NGZjMTZmY2EzMSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMGY3MTdiODIxOTdjNmQ1MmZiZjFkM2RmNGMxNTA3Njg5OTljMTU0YjNiMGU5OGY3NWQ2YTMyZGViM2Q3NzUxNyJ9" + }, + { + "resource": "repo://src/lib.rs#L416-L448", + "version": "repo-lines-v1:sha256:5ba29d6e1dc295a592f286f699df23f54c5ef6c0d53737da27b17edbc9c3f749:eyJzZWxlY3RlZExpbmVDb3VudCI6MzMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNmYTc2ZTkxYTdlN2YyNzQ5NzdjZjA5MTU1OTBlNjQ1ZjIxYWU1NWYzOTJlNWViOTNmNDUyZWQ0NWE5NWViYTgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjM1ZjZmNDY2ZTU5Y2ZjMzA3MDNjNDVmNzJhZjY1ZGU1NGFjMzYzZDM1NGNjMWEzMzMwMzUwYWNkNDUyNDM0YzQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjA3MWIwMTRmNzhlMDM5YTU0NzkyMDA3YWJmYTVjMmVlZDJmOGE3NWI4MGI0MWM3ZTljNmU4MmUzNmQ3Nzc1Y2MifQ" + }, + { + "resource": "repo://src/main.rs#L74-L77", + "version": "repo-lines-v1:sha256:83ad68daa6dacc35dfd808d3607bf212db9cdb1f903736a7bfbc8fee7e5af552:eyJzZWxlY3RlZExpbmVDb3VudCI6NCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYzE5ZWRiYzM2YzgyYjllMmJjOTNiZTViOWRmYTlmMDQ2ZjU1MDJjMzQxNDQyN2ZiODE2Y2ViMmQxZGM2NjA0NiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYWYxMmNiNTk1M2Q0YWVmMzk0N2MzNTBhZDk2ODIzMjdjNWFmNGU3YWE4MmUzMWZiZGJjNzZiYzQxYzUyZWEwNiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiOTk5YmRlZmVlYWNhNjM0MGUwMjkwZGM4NmU4ODljODlkNWUxODlhYTViYTQ0YjU4NWIzYjcwNzM0ZjRhNjJkOCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMzY5N2M4ZmQwMWY3ZjRhYjc3YTU5ZDA3MzMzZGZlODI5MDk5MjE4M2NiYzU5MjUxZWMyMDMwMDgyYTMzZmI4YiJ9" + }, + { + "resource": "repo://tests/smoke/run_app_quit.rs#L21-L29", + "version": "repo-lines-v1:sha256:2ca7aaa97361ab323d949cc8c47f31a66bdabdde8882ce8decb20278e33964d9:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZDJjZDRkYjQ4YTM1MGU4ZjBiNDc5NDRjNDZlM2ZmZGJiYTEzMmQyMjkwYTkxZDcwMTJiNTZlYjQ2YzhhYjRmNiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZmVlYTRmZjdhMzA5MTBiOGExMmY1MzEzMDdjNzEzOWZmNDcxMWI3MTkyOGViNDNkY2Q3NzUzNDY2M2FlZmVjZSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjAsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZTNiMGM0NDI5OGZjMWMxNDlhZmJmNGM4OTk2ZmI5MjQyN2FlNDFlNDY0OWI5MzRjYTQ5NTk5MWI3ODUyYjg1NSJ9" + } + ] + }, + { + "id": "claim_c2f6378abf17401ca7c5deece62c1ef9", + "statement": "The profile picker runs only when stdout is a terminal and no SSHUB_AUTO_QUIT is set (headless invocations select the last-used profile silently); the picker's intro animation honours the last-used profile's appearance.disable_animation via profile::picker_animation_enabled. setup_terminal enables raw mode, the alternate screen, mouse capture, and bracketed paste; TerminalGuard restores all of it on drop, and an installed panic hook disables raw mode before delegating to the default hook so a panic never leaves the terminal wedged.", + "evidence": [ + { + "resource": "repo://src/lib.rs#L127-L165", + "version": "repo-lines-v1:sha256:bab81ece97fb5702d79fbc2065f5c7bbf3b2925658d70a675705f1674f762e9a:eyJzZWxlY3RlZExpbmVDb3VudCI6MzksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjIxYTE3MWYyZTgwZmUyM2Y5Yzg3YmU4Y2Y2ZmZjNjUwYTlmMDk4ZmE0OWZlODU1Zjg2YWRkYWUwZjJiMGEyNGEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImJiZDViMmUzZjVhYjU4ZmZmZTRlNzE0OTJjZjE4NjMzNzQ3ZTBkZWM0MDU0YmYxZmI4YjgzOWUwZmI1ODQ4NWEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjEyMzg0NTU2ZTQ0MDM0MGM3YjdiZWMyOTZmODU0MzE5M2JkOTkwNWVkMTBmZTg4NzUzOWU4NTYxZTE5ODdjNWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQ2NWFmZGI4YWNlN2NjNTliMjRhYmM2Y2M0OThjZjQ0MmYzZmY3ZmRhNWY4MTU0ODRjYWUwNjZjOTY3YWRhOWQifQ" + }, + { + "resource": "repo://src/lib.rs#L199-L213", + "version": "repo-lines-v1:sha256:8c0308bbcedfde2be11d214cd9fb671a2d2026e59e4e308d88f203a05fae1fcd:eyJzZWxlY3RlZExpbmVDb3VudCI6MTUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA4ZmFjZGEwN2MxMGM5OTAyMjFlNGViZTRhMDg4MjcyYmYzZTA5OGM2NDAxNmYzZDZmOThlZTc1NGQyZDQ5MWUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjExOTE0NmE4MTNmZmUwMGZkMTAyYzA2NGE4NGMyYThjYWY3N2RiMTI3MTBhNWI0ZWI0ZmUzZGE0YTYxMWZlMWMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjFiYTkzNzdkNzdiNGJlYmQxMmMwYzQxMzdmYTExODE5YTZhYzA2ZTI0MzZiOTNjMjQ3MjIwZWRkNjUyZTllMzUifQ" + }, + { + "resource": "repo://src/lib.rs#L571-L608", + "version": "repo-lines-v1:sha256:9567b16c0a9aa5c127663e3ca6d6676f0c3fb4091d89abd46260175b78d51ed0:eyJzZWxlY3RlZExpbmVDb3VudCI6MzgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjIxMWY4NTdmODU3OThjYWFiOTlhNzRlODE5MmNjNDhjZDEyNzUxOTAwNWZmMjM4M2M5ZTZjMzliOTJkN2Y5YzEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjM1ZjZmNDY2ZTU5Y2ZjMzA3MDNjNDVmNzJhZjY1ZGU1NGFjMzYzZDM1NGNjMWEzMzMwMzUwYWNkNDUyNDM0YzQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjYzMTQ0OTFkMmJjN2Q4ODU5YjU0ZTc3MjZlNjFiYjFhNzI0M2VmMmU5Y2U5YzIwMGVhNmM2MjFkMmUwYTNiOGQifQ" + }, + { + "resource": "repo://src/profile/mod.rs#L501-L514", + "version": "repo-lines-v1:sha256:6bee2c77e6e0ebe583abf6d8b305b40137dfcd2b22667b1f98edbbbb210a6f53:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImEyYWY2ZmU0OTJiYzM1ZjExZDQ1OTMxYmJmZDhmMGEwZWE4ZWVhODY3OTkwNGMwMzRkMjE3ZDE4YWFiYmJhNmYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjIxNTdhZmZjNGE0NWU5OWMyODVlZGU4ZjM2YzM0ZWRiZjFjYTllZWQ4MmI3ZDg0ODQwODQyODExYjUwYmZjMTAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjRmNDM2NmFhNTdjYjM2OTIyNTViYWFlNGY2OWQ1OTY3MmJmMDY4YzhiMzY1ZjExYWY3YzgzNDI5NWNiNDVhYjcifQ" + } + ] + }, + { + "id": "claim_85b15e72ee2641e2b8e6d6fed4d8de43", + "statement": "AppDeps has exactly four injected fields — resolver (Box -> SshConfigResolver), metadata (Arc -> MetadataDb), store (Arc), password_store (Box -> OsKeyring behind NamespacedPasswordStore with a credentials-file fallback) — and they are the main test seams. The former fifth dependency TerminalLauncher and src/launcher/ were deleted in 0.10.0; the CLI `sshub host connect` path spawns ssh/mosh directly via std::process::Command, bypassing any launcher abstraction.", + "evidence": [ + { + "resource": "repo://CHANGELOG.md#L687-L693", + "version": "repo-lines-v1:sha256:fb5b3d5a5f321b39c39a926fd0a85a34e5290df1cfe901b7b67344b5e3d000b3:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMGM1MzhhMzQwY2I5MGI0MzYzZWYxOTk5ODAxY2YxMDAxMGZiNzBmMGE1NDJjODhkNGQ4NDkxYWVhZWFjYTE0MiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYTQ0ZWUyYTkwOWFkMTU2OGQwMjIwZWMxYjdkN2I1NGM2MWEzOTQ2NjY4ZmE2NTdjMWE0NDY3OWY4YjQ1YWU2MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNzA0ZjFhNzk2ODk1M2NmZDAyMmIyMmNiNGUyNmFkNDMyMDAyNGIwMDZkOTg0YzRhY2Q3MTllZWI4Mzg2MTQ2YyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiM2ExOTM2YWNjMDQ1Mjc2ZDBlYjZkYTA1YTRkZDhmOTMwMDBlYTY5MDQ4NjQ1ZDQyMDJiNWE4NjE4MjY4YWExMCJ9" + }, + { + "resource": "repo://src/app/mod.rs#L150-L156", + "version": "repo-lines-v1:sha256:4ec05c712ba921bd74fc79476df91fbb69485f64f5e14955f2eabf29ed178a96:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNDk2YzZmNmZhNjI4ZjFkNWU4YjQwY2RmYTZkMTFkMDk5Mjg0NzI0NGJlYmNkZWM0ZjI1Y2RhZmFjN2YxYTlhNCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMTllNzYzNTVjZjExOTk0ODBjM2Y5OTgzNmIxNDg2Yzg0MWQ4OTFmYWFkOTA0YzYwOGUwOTMwMjJkMzc1N2FlZSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiYTM2NjA0NGJlMmQ2YWE1MmFkZDEzYTZmM2EyZmE2YTVlMDRmYzlmOTllNDkxODU2NzY0NjQxN2RjZWJlNmMyMiJ9" + }, + { + "resource": "repo://src/cli/host.rs#L140-L240", + "version": "repo-lines-v1:sha256:bb59db0feb660bfb414049fdf15e38b5116df80f96e8b9feb81700df2eab1b04:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAxLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI3OGU0NmFjY2ZkZGVlNTZkMmFiODk3OTIyN2RhYmNjYjc2Mzk2M2VhOTNiZDVkYTczNmU4M2RlMmM4YmZiYjk4IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiJiMWIwNDRmMjljYmRkMjlhOWJkN2YwZmEzMmFiNTAwNmFmMmRjMjA2YzE1YTcyZTAyODE0YTAyMTE2MzRjNzk3IiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiIzY2RiYzAzYmMxM2U4MDIyZDMzMDc2ZjA4MmYwZTIwZmE0N2JhMWNlNjkxNmM4YTZhZjQ0ZDMxNWNkZDQ3MzI4IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiI1MWEwZDZhNTA2MTM3NjdmMGE4ZGRjY2I4MWQ2NDZiMzcwZDI3M2Q1NjNmYTU1MmYwZGJlZDMxODUzYjk5NGE5In0" + } + ] + }, + { + "id": "claim_a7f1562ab44a49a7879e90ac67f43267", + "statement": "AppMode has exactly 38 variants; every overlay is a mode, and handle_key dispatches on self.mode to per-mode handlers (with SessionPicker, SnippetPicker, and Connecting/Session routed early so a live PTY keeps Ctrl+C). is_overlay_mode treats everything except Normal | Connecting | Session as a popup, which is what the render pipeline's popup snapshot slides key off.", + "evidence": [ + { + "resource": "repo://src/app/keys.rs#L4-L20", + "version": "repo-lines-v1:sha256:916f0eb4676897344d98f1dfe74859f829308c3d7deff62208f4869ae608b929:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI5Yzk0NTIxNDI2NDhmYTg3MDExN2U2YTFmMGQzOTJjOWEyNjNjZGI1OWRiOTI3MjY1OGQ4YWY3YjhhODk0ZDEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjA5N2YxYjQyNTcyYzZmZTZiMjFhYzRmOTU4MWM3ODRiYjE3NTAxNTkxYjI2NGQ0Y2I0NWQyMjE2ZmVmZWI0MzMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjYzYmVjODUzOWIwYmJjN2QxNzVlMjk0OTZiM2ExZWI5MTYyMmU0ODM2OWY4MzBmNTc3MmQzNWI2Y2QwYTBkYWIifQ" + }, + { + "resource": "repo://src/app/keys.rs#L81-L126", + "version": "repo-lines-v1:sha256:9269e3bd9bca9451ba12c8331d454f2fd2dc054090e08b0f65094e16849526cb:eyJzZWxlY3RlZExpbmVDb3VudCI6NDYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjJjN2Y1OWU1NWUxODUwZTE5MDJhNzJlYWMyOTJjNzIyNTJkZTk2M2JjODdiOWZkOGJiNmJiNmVkNzRmYjc2NzciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImEyZmNiM2M4YTY2M2U5MzZjYTFiOWFmMWM4OTUzYjJjOTlkYTU0N2Q4Yjc0MDhkMmVhYjJhY2M3MWFiODFhN2QiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImE2MjZjNWVlYThlODZhNmM4OTVkNWI1MTM2MmQ3YmE2ZWM1NGU5YzdiOWVkNGQyNjRhN2Q0NGViNTg3ODk2MzAifQ" + }, + { + "resource": "repo://src/app/mod.rs#L491-L504", + "version": "repo-lines-v1:sha256:82ce174c3c23acaa0ebcfbea07e4898e808ceb5dfdbd3228fc030a97e66a41d8:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjVmNzdkZjBhNzVhYzViODhjMGNlZGJiODg5YzQ3OGY3MGQxYmNjMjk1M2I2NjIzOTVjZTRhNDU0MTZmMjAwYmUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjgxMGUwYzZiMDE5MjBkOWM4OGJkMWI0NjFiMjNhYTM5YTUzZWE1MTE5OTVkMjU4MGFlNDNiMDQ2MTRmYzcyNGIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjRmMjZhMThmMDEyZmJjYzNhZDQwMjU3ZWNhYmZlODU4YWY1ZjA0ZDZmOWYxYzAwMzY2YTg4ZTkxM2ZmNzNlNjUifQ" + }, + { + "resource": "repo://src/app/types.rs#L367-L433", + "version": "repo-lines-v1:sha256:7e1fe9029b76288b2969e2f2b2f6ce7bfb34a9516cbe3ff326435a5c8d13c68a:eyJzZWxlY3RlZExpbmVDb3VudCI6NjcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU3MTZhZjcwOWZlZDM0OWNiNzdjYjRkYTYyZjk2ZjI3NTEyMmE4MzEzMTRlNGM1NmNkZjdlOGI4ZmZhNDFjZTciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImM2MzdkMTlmZmFiMzc0ODU1NjY0YTYyMDQ5MDNmYWFlZmUzYzAyNzliMTc2NDc2OTk2ZmFiZDQ0YTIwMTg4NzAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjA3YjQxZGQxOWEzYzMxZWEzNGQ2MDg0NWUyZGE4MTMzY2I2ZGFiMzNkNjVkZThmNmE2ZTMyODdjNzk5ZDlhYWIifQ" + } + ] + }, + { + "id": "claim_51e2856e0b82475789f19943706a6f95", + "statement": "App.active_tab is a bare usize (0-4 = hosts, sftp, tunnels, identities, audit), not an enum — there is no type safety when adding tabs; render_tab_body falls back to the hosts body for out-of-range indices, and the anim_prev_tab mirror lets detect_tab_switch notice tab changes made by the many code paths that assign active_tab directly.", + "evidence": [ + { + "resource": "repo://src/app/mod.rs#L296-L301", + "version": "repo-lines-v1:sha256:38cb23e52170292b0a0c6e4ad6b21598ae687fa2c6a787aa9fea4be471bfd08b:eyJzZWxlY3RlZExpbmVDb3VudCI6NiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiY2NmNzVlYjk5NWY5ZWEyNjgyOTYwNzgxNzVjYWE1NDRmOWFiYjcyZTFjNGFjMzU3ZmE1YzczNjI5ZTJjMTI4YiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiM2ZmYTI0ODQ4NzM1ZWIwMWUwZmI4MGRjMGVjZGU3ZjQ2MmM1MDFiMjQyMjU5M2VkYzVkYjMwMTI2MWIxNmE0ZiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiODJlMWM5N2JkNDViNjRlOTE1ZWI5MjAzNDkyZGEyYzYwNjNiODZiNzQ2ODVhOTI1OTRhOWJlMmMzZGFkN2Y2OSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZWVlMWUyNDEzMTE1NDU1YzZkNjI3ZDE3OGU3YTkyMDhlYmUyZmYxNTFkNzBhODBlYzllNjBkY2E4NzAxZDNhNCJ9" + }, + { + "resource": "repo://src/app/mod.rs#L539-L551", + "version": "repo-lines-v1:sha256:74365c0ed6e3f1a4ad092ae0ea5936c231df7fe9bbeff609daaf88b956cf82cc:eyJzZWxlY3RlZExpbmVDb3VudCI6MTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBkOTdjMTYyMGI5OWU2MGYzYmY5ZTZlZWU0MzhkMTkwNDQ3MjcwM2I3OGEzZTk2MDMxYTZmYzAwMjM1YjM4NGQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImIwNjJjMjc5M2IwN2MzNTNhNmVkNTI3NzIyNzFjNmNjYzI4ZTY3NTFkMWE2MWVkYzIyNzk0OGRiZTkwYzBkNTEifQ" + }, + { + "resource": "repo://src/tui/mod.rs#L1543-L1558", + "version": "repo-lines-v1:sha256:256c6c307bb30894cbb29f68ca345493fa42f89caffb97a60059e06be5be6254:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQzZjQzNjE1ZjA3Mjg5ZDljMWM0ZjM0ZWJiYTYwYWU5Njc4ZDRlMmQ0ZjJkMzIzYjNlMjJmZTE2N2EyYTNiOTEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijg3YTQ2MzRmNzgzMGMzZmJjNjA2OTdkOTYwY2U5NDJjNGQxYTgxMzAwNTQxMDM3NWI4YTkzYmRjYWVhZWU1MzEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImVkOTFjZGQ5Y2Q2YTQ4NmQ4ZmE5NTI4ZmRmZDYyNGRmNmIxNzQ2N2Q4NDExMmZhZGEwZDkxZTFkMDU2NTQyOTcifQ" + } + ] + }, + { + "id": "claim_a250e7cda3324368ad6ad5b6be36bbce", + "statement": "App::new_with_deps (the test constructor) uses built-in themes only, reads no config directory, and spawns no workers; the OS-detect worker is started only by the on-disk constructor new_with_profile, so dependency-injecting tests stay offline and never leak a live ssh-probing thread. First run with no hosts drops straight into Help mode.", + "evidence": [ + { + "resource": "repo://src/app/mod.rs#L846-L883", + "version": "repo-lines-v1:sha256:212f757584d5ca93bd9811860cec26387916def522e56e9b8068bfb7d089704c:eyJzZWxlY3RlZExpbmVDb3VudCI6MzgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImIwZGRiNTdlNGQ3MDg3OWQ5MWUyYWJlNTBjYjBhNDYzYzY0ODAyMzI0ZWNjMTQ1NjIzNmFjMjJmZDEzZjZkNjIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjhlMDQ5MWU4MmVmYzQ0M2E0YzU1NjY4MDU4MjhmMGIxZGRiNzExYjJjMjBhYTlhMTBjZGYzMmMwOGMxZjQ1OGUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjI3OGViZTU5ZThiNDRhN2RmN2IwYWY1NGVlOWNmMzBhYThjNmUzNmNkYjhlNjJhNjMxNGYzY2EwN2I5OWYxOGYifQ" + }, + { + "resource": "repo://src/app/mod.rs#L885-L890", + "version": "repo-lines-v1:sha256:2581d80fee624d56af2a2cfd7e0731329d8e757f4fed66551714d20563af5d9c:eyJzZWxlY3RlZExpbmVDb3VudCI6NiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMjE4ZDdmNDExMDFlNWE2ZDg0YjNhOTA5ZjQxZDRhMzExNzgxMzBhMjc0MDY1MWExZjZmNjdiZWZhMTI4ZTVhOCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNzE0ZTkwN2QyNGMzOWIxNzM5NDYyMmFiNGJjOGYxNjk3ZDhiNDMyMzYzYjg0MGY1MTQwYTQxZGE3ZjAwNmY4MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiOWMzMDFmNjMxNGYzMjRmZjM1ODFmMzM2YmVkZmE0MWJkNmQ5N2Q1NWFhNmU1ZTYyMDIxMDlhMmQ3MjkxZGNkMiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiYzA1MzQ5YzFkM2Q5YTQzNGUxOTdkMDdlMTI4MmYzZWNkYmZmM2I1ZWFlMWUwYjhhMTczYTMwMWI0OGQ5ZWE2YyJ9" + } + ] + }, + { + "id": "claim_a76e920d6c5c4d76999d1260707891c9", + "statement": "App privately owns the ThemeManager (no global mutable theme state); renderers read app.theme(), and the only two mutation paths — App::activate_resolved_theme and App::replace_theme_manager — both run invalidate_theme_visual_state(), which drops every buffer snapshot and in-flight slide captured under the old theme before the next frame, because the background passes select cells by colour and the slide blits would otherwise composite cells from two themes.", + "evidence": [ + { + "resource": "repo://src/app/mod.rs#L170-L184", + "version": "repo-lines-v1:sha256:65b2cdca57d38748db13c898ba5ef31f333af584cb790c54d00c9fbf53e1d456:eyJzZWxlY3RlZExpbmVDb3VudCI6MTUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImEyYmJlOTFjNzY0YzAyYjNhYjI0YTk2ZGFlY2ZlMTk1Zjg4YThjNTk5NTM3ZDYzY2JmZjI4MWRiZTNkMDY2NTgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM3OTY4MmQzMzFiYzA0ZDEzNzExZjdhZDAxZWY5Mzg2M2M1ZmEyZmE2N2UyZDNhZDEwNjBkYTJjMTcwN2EzMDUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQ1N2M5MzBlMTVlMmVlODA0NGMwZTZkYmQ4MjUxYTg2MDYxYjkwYTUyMGE1ODRmYmI1Mjk4ZTBlMTMzY2YxNmUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjdkZDVmMmFmNmJmNjFjM2FhN2NmOTIwYjE1MDkwNWEwOGU0ZjAxYjM0OWQ1NWRmNzVjOWQ2NzA5ZTNmNzczZGQifQ" + }, + { + "resource": "repo://src/app/mod.rs#L700-L750", + "version": "repo-lines-v1:sha256:1b0b0f3d1f571f9bf3a500a7ab523c7851156db396cd78645cb158812037c22e:eyJzZWxlY3RlZExpbmVDb3VudCI6NTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjVhYTZlMWZhMmQ2MjIwNzc3YmY5MTNmZDYwZjI1Y2Q3MzQzYmYwNjlmN2UzNWJlNGZmMDBkNzhiYTVkNGFiODQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImIxZTk4OTk4NzgyYzk3YTkyNmM3NzliZWEwMDExOGI3MGFlZjY0YzM0ZTZhYmFmNTFlNmU5ODZiNWIwOTJmMDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijg1YjY3MzZhNjAwNDFmODJmZWQ1OWE3ZGNiNGVkOGVjZTUzNzI1MGQ1ZDk0NjhjYWJhOTY3NDIxNDE5ZGI2YzQifQ" + }, + { + "resource": "repo://src/app/theme_picker.rs#L339-L349", + "version": "repo-lines-v1:sha256:7070e6181c1fe1195836ec383320217147bd63eeefcda333db3508b66e601fc8:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkwY2M1MGQ4YTYwZjI3OTA2YmFiY2RmNGZiZTZhYTU5MzkzOTk1NmQ1YzY1ZGIzMmUyMWEwMGMzYTRiYjg4NWIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRlNTE2Zjc4NTAzYzNjOWFkODk5NzU4MDVjOGZiZDM3Mzc3YzBjNTFjZWQyMzI2MjBkZjZmZjAxMDcwZjRkYTciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjFkZmU4YzgxZTBhZGYxY2Q2YzM3ZDk5OGJkMTY0OGIyM2I0MGZlM2NkZjhhYzQ1OWU1N2JhOGU4ODEzMjdjNjgifQ" + } + ] + }, + { + "id": "claim_017d514b4ca948008381853101390862", + "statement": "The theme fallback policy lives in ThemeManager alone: an unknown or unresolvable appearance.active_theme degrades non-fatally to the embedded `default` theme, keeps saved_id verbatim, and never rewrites config.toml, so repairing the theme file restores the user's choice. load_themes_from is deliberately infallible — an unreadable themes/ directory degrades to built-ins plus a one-line notice and the degraded manager keeps the failed directory path so a later reload can find it.", + "evidence": [ + { + "resource": "repo://src/app/mod.rs#L752-L791", + "version": "repo-lines-v1:sha256:792635eee70182c05af6f97bbdb5cf6ebc19314acc78a76da3e6f154b5d27710:eyJzZWxlY3RlZExpbmVDb3VudCI6NDAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijc3YzJlNWYwNmYyMmEzMDIwNWE2NTAwNWM3YmE5NzVjZDhmZmIxMzkwZWE3ZWIxYmVjNTUxYjU1OGUzMTQzOTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjczYzNlMjNmOWYxZDZjZmY5MWYzOTBkY2M4YzdkOTU5MTAzZThkMjVlMzEwNWE3N2I4OTQzMzAzODBjNWQ2ZTYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjdlMzhmOTI1OWUzYTNmNzc0YjY1NzEyMWVkYTViOWUxZGY1ZTYxYTQ4ODBmZDMwNDJjMTk5YzM5YjUzM2UzN2QifQ" + }, + { + "resource": "repo://src/theme/manager.rs#L16-L17", + "version": "repo-lines-v1:sha256:7f38832cc0910b0c963eaafbda0757e423334141d6ca8986e7e4ee636b4614d7:eyJzZWxlY3RlZExpbmVDb3VudCI6MiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYzRlZWRlNzFiMjExMDk4MjQ3NWI1YmFmMzQ0NzRiM2Y1NGRhYjMwYTdlMjU3ZDk3MGYwN2ZmZjZkMzgzNTU1ZCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZDRjZjZhOGQ2NTg1NTU5YmU3MmNjN2U5OTYyOTkwMzM1MTM1MTVjOTQzMjQ3MWEwMzc4ZTliNjgxZjI1Mzg0NCIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMDY0ZGQ3NzBjNzc3NzRhOWJhZGYxMmU5YmE5ZGYyOTUxYmQ3ODRiZmVhNTJkMzQ2ODFhZmJhMGNkNjM2NmUwMyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNTlkMGFhZTcxYzI4NmNlZmM2NmRiZTA5NjVmMTMyNDllNjcyMTNhODZjMmIwMDVhNDg3YTg2OWQzODkzYmRmYiJ9" + }, + { + "resource": "repo://src/theme/manager.rs#L68-L114", + "version": "repo-lines-v1:sha256:ca1e0ac0711a35fdd9a0f2fe3be15aa7d6e67babb4b823075199d6761be7022f:eyJzZWxlY3RlZExpbmVDb3VudCI6NDcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijc2ZTkwZWYyOWQ5MDU2NjExYmE5ODA4ZGUwOTE2ZGU2ODg1ZmQ0ZTg4YjQ4OWQ1NTBlYzk1MWNlMDA2NjRlYzkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImM1Y2RlZjgwODVhZTc0YWI2MzZkNjQ1OWY1YzZkNWZiZmIwZGIyNjRmMWIwZDc1YmFjNTE5YzliM2Y2NzE3YWMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImU5YjU1NmQ0ZjU2YzRmZWFkN2U2YjRiZDg2ZDAyMWMwNGY5M2UwNTljMjg4ZmYyNzA3N2IxZGEzNGQ3NGRiYmIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImM1YzYyZDUzNzdiN2IxNWMxOWE0NzU5MjNhZWQ2MTcxZDNiZjAxNjAyNGVjNDM5OTA2ZDliM2NhMDI1NzFjNzEifQ" + } + ] + }, + { + "id": "claim_b9f54298e303475688c78ffcbfa7f1b2", + "statement": "The ThemePicker overlay previews a theme on the whole UI on every navigation step but only Enter persists: activate, write config.toml through the profile-aware writer, then mark_saved() which adopts the derived active_id (activate id is derived from the theme, never a free parameter). Esc restores the original theme by id, falling back to the Rc captured at open, and `r` reloads the themes directory, replacing the registry and keeping a tombstone for rows that vanished.", + "evidence": [ + { + "resource": "repo://src/app/theme_picker.rs#L145-L168", + "version": "repo-lines-v1:sha256:8cc9ce3acad47235f55be06a1c4f3d425afd96b25d05f62c57236e129bb7f47f:eyJzZWxlY3RlZExpbmVDb3VudCI6MjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRjOTk1ZDBmZjIyZDRiZjUyYjZjNDM1YjMxY2Q0ZGFhM2U1OGE4ZjVkMzAxMzgwMjk1MDAyZWY2MDMzOWIwZjYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImI1ZWFmZTA5ZmU5MzRjZmRiZTU4MGFkNGFiOWZiYWQ3ZTk3NDU0MTFmYzZiODk0NmZmMmNkYzM3YzY2YmUxMzEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjcwMmIwYmM2NWIyZjJkZTQ5ZDI4MWQxMzUwNWI0MGQ3OWQ0NTczYzU1OTYwOTEyY2U4NWFkZWEyMjA0OTNiNTYifQ" + }, + { + "resource": "repo://src/app/theme_picker.rs#L351-L443", + "version": "repo-lines-v1:sha256:07b36736f8309f26462194ac16bc177cdc176dcdd39adfc1c855f0894478ffe9:eyJzZWxlY3RlZExpbmVDb3VudCI6OTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI2ZWRmZTlhNmEzYjMwYTAzNjVhMDk5MDlhOGIxYjk0NjMzOGZjZTkzMDA2MTY2ZDk1MmRmNmI4OWNiMjE1OWIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjYyZGEyMjJmYTU1MzZmMmYzOTAzZDUyODNhZDk4NDFiOWY2NzJjZWVmZjBkYmI4YTQ3YTZkZTU1NzFlMzNmOGMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQyZjU2MzVkNThjNGNjMTUxZWJkYTM0NjUxZGU5ZDFhZDkzMWZhMDVkODRkODZlYjhmNjAxNWE5MTEyNzZmZjkifQ" + }, + { + "resource": "repo://src/app/theme_picker.rs#L445-L460", + "version": "repo-lines-v1:sha256:fd6a064cff79c4926aed6f6cdb2c2b4478cddd50e8a306ce612522ded47d765d:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU1MGYwZmQ0ZTQ1MzYwZjU4NDc2MWFkZTkwY2RmZDY2OTUzYzNiNjI0ODBjYzNjODllYzk4ZjVlYzlkYmQxYjEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjdlNGVlNzJjN2M1MTNiODVmYjYzMDUxMDg3MWU3ZWRmYTg3MjA5NGVlMTA1YWY5YmY5ZmNmOWYyNjQ5NGFiZjQifQ" + }, + { + "resource": "repo://src/app/theme_picker.rs#L462-L500", + "version": "repo-lines-v1:sha256:c3363372f09dd44f98bcfe8224feaef9e0afd8b46efd88fc4cd73007d565a592:eyJzZWxlY3RlZExpbmVDb3VudCI6MzksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjgxYjA4MDRjOTQzMDY3YTczNDZjZWY1N2YzMGZlZThjNzNiNDlmNGQ1YjNlZDczMmUzZjBmN2VjNzZjMWYxMmYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQ2YTRiZmRiNWE0NWU1YmQ1MzcxZDI2YWUwMDNiODc0MGY3MGY3OGYzMDU5NjEzZTczZTFlYjUxNzE5MGEyNDIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImVmMGE5MDdiNTZlYjMxYTEyNDk5NGUyZjhiNzkyNWIwNTRmMDkxN2YwNTk3YWJmZDcwZjA4MDdlMzhmODRkYWQifQ" + }, + { + "resource": "repo://src/theme/manager.rs#L190-L219", + "version": "repo-lines-v1:sha256:84fba8dac071136b3212ce23ad820255ea963018fa2a92966fde34ec9d95d8a9:eyJzZWxlY3RlZExpbmVDb3VudCI6MzAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRmZDg1OTc3Y2ZiNjJkMWE0YzllN2E5MDc4ZjMyZjNiYmI0NTc3NmIxY2IwZjc4NTU3YmVhZmM4NTkwMTZlYmEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNlZjFjZDVlOTU4ZWI4ZWMyOWY0MjY2YmUxOWFiNjEzMzY5ZTU3ZTBlODRhNmIxMzdjMTI2MmE2YmJiN2U0ZWQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjUwNGYxZWJmMjg4MjBmM2M3YTY4MWUzYzFlY2FmN2FiZmJlNGNkYWE4Y2E3YWVkYTVjMWEzNzBmNTllZWIzMTEifQ" + } + ] + }, + { + "id": "claim_c0060d690bc54a7baaa775fcd5a819e8", + "statement": "App::theme() returns the active theme with its ground released when appearance.transparent_sshub_background is set (derived once per activation alongside the active theme), while App::base_theme() returns the theme as authored and is what the remote PTY grid reads — keeping the SSHub-surfaces transparency switch and the session-grid switch independent.", + "evidence": [ + { + "resource": "repo://src/app/mod.rs#L653-L676", + "version": "repo-lines-v1:sha256:4a02a24167e9e43007b53b998b0f86ffdf24f22f36217f63120c0eb7e05ba912:eyJzZWxlY3RlZExpbmVDb3VudCI6MjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRmYmQ2MGE3YTg0NmQ2MjdmOTYyZGZhOWVkNzc1ZTNjZjk5MzY1ODUxNTIyYmIwMzk4OTUxN2ZlNmJjZDBiMTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImE0MWZmZTQ5MTFlMzI0ZjUxMTc3NTRmYjU4YmU0OTIzNjVhMTNkYzdiNGU3NGY2YmYwNTk3ZDk2YWU4OTczNWQifQ" + }, + { + "resource": "repo://src/theme/manager.rs#L132-L139", + "version": "repo-lines-v1:sha256:92e68241952de8ffe541208c960170d5f1c51aef902f05299436f8cd223a0cc5:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiODA0YTk1MDhiMWE3MTU5OTQwMDY4ZmVkYmYzNDY3MDhlODU4MDYwNGJkNzE5NTlhZjAwMWM0ZGMwMGZiZGEzMiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYjU4NTc0ODc2NzUwYmIxN2IwNGM3ZjkxNTM3ODVmMWM5YWM4OGI1Zjk2ZDY0MDJhODM1MGZhZmI5YjQ0MWRkNCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiM2RiNTU0YjAzNmY5ZGFmNjQ0NTZlOWU4ZDZiYjkwZDk0YWRlMmU2MGUyYjIyZTc4YjU5NDExNDhjYjdiNTRjOSJ9" + } + ] + }, + { + "id": "claim_9bbe0ae522404d87beea6e7a2918679f", + "statement": "The pre-theme-system fixed palette survives only under #[cfg(test)] as src/tui/theme.rs `legacy` — the hand-transcribed witness for the `default` parity proofs; productive code can only obtain colours from ResolvedTheme. What stayed in src/tui/theme.rs is logic, not colour: the string-to-status-role mapping and the sparkline glyph ramp.", + "evidence": [ + { + "resource": "repo://src/tui/theme.rs#L1-L68", + "version": "repo-lines-v1:sha256:c92ff5d0698d1275d5788405455f6767812c51c681dabaab5f27c55095461701:eyJzZWxlY3RlZExpbmVDb3VudCI6NjgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRkNjllNDJmMWE0NGJkMzdkYzIxYTllMTUxMzc3ODlkNTE3YzIwY2NkYzAzMjQzM2JlMWRhZWU4OWI4ZDVlNjYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ2ZmUxYzNlYTQ2MmFiOGYwZDQ5MTNmNzFiNjIxYjFiZDlhODhlZjBkMmVjNzk4NGE2N2I0YmYxYTUwMWYzYjAiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxMjliZWVkYzhkNWMxMTE0MmU2NGJiNjdhYTliZTJhZjRlM2FmZTZiMDA5MGI0Nzk4MjFkZTVhNjJmMTcyNGIifQ" + } + ] + }, + { + "id": "claim_7c3289e3723a4d1c93f8bce3be5b344f", + "statement": "tui::render resolves a FrameComposition once per frame from a single clock reading — the session enter/exit slide offsets, the session-tab slide progress, and the `protected` regions carrying remote output — because the slide's blit and the protection of what it blitted must agree or the leading columns of a session slide get painted with theme background. The pipeline then renders the session view or dashboard, refreshes session_snapshot (in session view) or dashboard_snapshot (off it), runs popup snapshot/open/close passes, apply_app_background, apply_panel_selection, and the splash fade.", + "evidence": [ + { + "resource": "repo://src/app/mod.rs#L307-L334", + "version": "repo-lines-v1:sha256:2eb5f29be9521883d22c548d96b95d3bf635de3a42a7c234f3aab73dc8f8fb1f:eyJzZWxlY3RlZExpbmVDb3VudCI6MjgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImM4NmY1MjYwOTVlMDJiOTBjNDRjZGViNmUwODFjMTg1ODY5MzEwZjA0YmQyZWY5MWNlYWE3ZDVjMzQ4N2I1M2YiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBiYzAzNDJlMjE0ZGZlN2FjNTU1MmU5OTI4YTI2Zjc0Y2I4ZjJlOTk1NjU2NjdmYmI5MzA4NjZhMTVmZmViY2QiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjUxYWFjMmFjNmE5MDIyODJkNTgwNjRiMjgxMTg3ZDU5YzQyMTQzMTI5ZjM1OTZiNDNmZTIzOTJjODlmMWY3YmEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImJmYmEwNzYzYmU2NjJhN2M0ZTkyNmRiYWRiZmE1NGMzYjQzNDZhYzE0MjQxZTIwMzZiYTRjZWU5NTM0NDNiOTIifQ" + }, + { + "resource": "repo://src/tui/mod.rs#L111-L194", + "version": "repo-lines-v1:sha256:73c2d4fa002d14fe50325d00aa3d1a45c0a7ed51e48d6792fe2d9f048068b9f4:eyJzZWxlY3RlZExpbmVDb3VudCI6ODQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRiZTcyY2M4Njg1ZWQ4NDRjZjRiNGYwZTI4ZDk2OWM4MDdiYTk3NThlMDVkYzhlYjc0NWRiYmY2MDcwZDg4ZTgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQzYmE3MTU3Y2I5MzE5YmRkMjQyZTkyMGMyNzY3OTgwODgxMTk4NjAwNjAzOGU0Y2IwY2YyZDZkNWNhNDg4OWYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQ5OTYyNzU4YzhjN2JmNzNkYzQ1NWYyYTNhZDliNjc1ODczNzViMjE4NmUzOGEzY2NiNWQ0YzM3ZWQ4ZWZjNmQifQ" + }, + { + "resource": "repo://src/tui/mod.rs#L370-L452", + "version": "repo-lines-v1:sha256:7f01d60ff4ddefe3ef64727ad92101d13dfdfaf4004d5a5e9fe4f87d1552d6c6:eyJzZWxlY3RlZExpbmVDb3VudCI6ODMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkyMmU0NjQwNjRjOGI1NzE2ZTIwNmU5ZmFmYTVkOGQ1YWVkMjYwNjg2NmQ3MmYyM2MzMTMzM2NiNWIwNDU5ZTYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUwMWY4MmE2NDkzMGJmNTAxMTZiMjcyZTIwZGNkNDc2YTI3ZDkwYTk5NDc2NGE4NmE1ZTFiOWMzZWZiY2ZkNGQifQ" + } + ] + }, + { + "id": "claim_b5a9f1a1af60450c990d578acb00e26d", + "statement": "Overlay popups always draw at rest through popup_open_rect (which records the rect in app.last_popup_rect); capture_popup_snapshot snapshots the popup each frame, render_popup_open drops a fresh popup in from off the top of the screen (restoring the captured dashboard backdrop behind it), and render_popup_close throws a just-closed popup's snapshot upward, both over POPUP_ANIM (260 ms). Overlay-to-overlay transitions (e.g. a form bouncing to its discard-confirm) replay neither. fit_popup clamps with desired.clamp(min.min(avail), avail) because a plain u16::clamp would assert and crash the TUI on terminals smaller than a popup minimum; open_popup paints Clear plus the theme's PopupBackground role.", + "evidence": [ + { + "resource": "repo://src/tui/mod.rs#L1284-L1384", + "version": "repo-lines-v1:sha256:c3356c229eba2d9f7663fd1e78195ac01b9a98bd01b0951fc57b1e54481d0669:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAxLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI3NTM5Mzk3NzBjNTYxNTNlYmI0MDNiYjZkZDMxYWNkMGI0YTYwN2Q5MWRmNWQ3OWM0MTdiZTUwOTFiZjU0NjRlIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0MTJjYTM0NWNjZjc1YmY5YzA4MDZiY2U2OTViZThkZTgwOGI3OTk4NDI1MWE3YTU0ZDIwMmNmNjEwMWRkNDUxIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiIxMmJlOWRjZWE2YTkwMmRjYTA3ZjhmMmE2ODIxZDY2MzNhMzM5MDQwYzAxNmJhMmEwOTBmNTVhZTVmNDVkNDZmIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiIyYzNkNDg1MTIwY2QyNWU3YmY4ODU4ZTQ3NGZkODQ0NTQ1NjEyZDE2YmQ3NWZmOGY1ZDBhMTczN2YxYTA0YzRlIn0" + }, + { + "resource": "repo://src/tui/mod.rs#L23-L29", + "version": "repo-lines-v1:sha256:a91e4b4036ffa58331a0168279947732ce23aff8b1feca571956c2b6f60082fb:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYzBlN2I5Y2UzN2Q4MDE5MDUzM2YxN2NmOTM5OGUxMWQ1NTQ4OWE3YWE3NjFmYmFhMzFlNTJlMWIyYjE1ZGJkZCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYTQyOGQ5ODY3NzdjMTU5ZmU4ZjdmNjA0N2ZmMGNmZGNlMjFmOTM3NGQzNjAzYjAyNmFiZWZhYmYzMmY5ZTFkZiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMmM2ZWZiM2U0NjcyYjFiZWMwNzMxMWY2M2VlMTg4ZjBmYzdjNjk5NjJjOTkwZjNkYTA4M2EwOGJiZTc2NzdiOSJ9" + }, + { + "resource": "repo://src/tui/mod.rs#L76-L97", + "version": "repo-lines-v1:sha256:97a952ffb05cb77983a327e3a0970a9853d6bf329feabb2aa3acfa8ac83674ee:eyJzZWxlY3RlZExpbmVDb3VudCI6MjIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImUwMGUzZjc1OGQwZDA2MGE4MDdkNzlmNzA1NTI2NjM1MzUzODg4YjYzYjEwMTQ4OTY4NTcyNTkwZGRjMGIzZGQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQ1NDQyZjMxNTc1NjZmYTk4ODYxOTM1NDgzMzRjNjRjOGE5MGY1YzM5NTg2ZDkwYmE3MTA2NDhmYzk1Y2NjODgifQ" + } + ] + }, + { + "id": "claim_ac7942527e8446a68792bd917dde31c1", + "statement": "apply_app_background backs still-Color::Reset cells in three separate passes after all widgets draw: (1) the theme's components.app.background (solid or gradient) paints SSHub's own surfaces excluding protected PTY regions; (2) apply_pty_ground backs exactly those protected regions (resting viewport plus travelling slide bands) with the theme's semantic.pty_background/pty_foreground pair, filling both channels with per-channel fallbacks and skipping entirely under transparent_session_background; (3) semantic.canvas fills whatever remains for themes that resolved their ground to \"terminal\", skipped under transparent_sshub_background. Every pass selects on Color::Reset so no widget-coloured cell is touched, and excluding the protected regions is load-bearing under transparent_session_background.", + "evidence": [ + { + "resource": "repo://src/tui/mod.rs#L196-L341", + "version": "repo-lines-v1:sha256:0916a666f258e0e04cf1075763f387eebd93fe8d28ec07d56f668a60f4c398f9:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQ2LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiJjNzhiNjk4YmI3MjljZmQ5NTI4ZmRlNDgyYjYzM2NjOTZjMmEwMDIyZTdiOWZmYzg5NmI3NDk5NGFlNWYzNTdjIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0MTJjYTM0NWNjZjc1YmY5YzA4MDZiY2U2OTViZThkZTgwOGI3OTk4NDI1MWE3YTU0ZDIwMmNmNjEwMWRkNDUxIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiIxOWU3NjM1NWNmMTE5OTQ4MGMzZjk5ODM2YjE0ODZjODQxZDg5MWZhYWQ5MDRjNjA4ZTA5MzAyMmQzNzU3YWVlIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiI0M2M1MjNjNWFhNTllZDYwMzMwOTNiY2YzZjg2ZjM4NjJhOGM4NWRjMGE4ZDdlNmVhYjlkZGJhMDU3Nzg2MTgyIn0" + } + ] + }, + { + "id": "claim_4a69182baa38456298c8e425b905b6ae", + "statement": "The animation layer is two dependency-free modules: src/tui/tween.rs provides SlideAnim (eased Rect interpolation with Easing::Out/InOut), progress, color_lerp (RGB-only; non-RGB colours switch at the halfway mark), pulse_now, and wall-clock braille spinners; src/tui/blit.rs provides the cell-level blit/snapshot/fade primitives behind the slides (blitting cells rather than shifting a Rect lets layers travel past the screen edge). Named durations gate each effect: SPLASH_FADE 360 ms, CONTENT_FADE 140 ms, SFTP_NAV_ANIM/SFTP_QUEUE_ANIM 200 ms, PING_FLASH 420 ms, FOLD_ANIM 180 ms, SELECT_ANIM 120 ms, TAB_ANIM 220 ms, POPUP_ANIM 260 ms, SESSION_ANIM 280 ms, SFTP_ANIM 260 ms.", + "evidence": [ + { + "resource": "repo://src/tui/blit.rs#L1-L120", + "version": "repo-lines-v1:sha256:7623a7c79fd13e638baa175ce26310f84f02c34038bbe38bb881e7cf5be60c3c:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIwLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI0YjEyZWZjZTAwYWQxYTIxNjAxMTZiYTYzNDQ4YmJkZWVlYjcxNWZkZDBiYzJiM2YzYzQ5YWFkMzBmOWZkNjRlIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiIzOGEwMjlmNDUyYWQ5OTNmYjEyMDNkZGFmZWE1NWU5M2NiNWI3Zjg2ZDk1NWI1MGYwNWViN2I0OTdjNDNlZDJmIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MCwicHJlY2VkaW5nQ29udGV4dEhhc2giOiJlM2IwYzQ0Mjk4ZmMxYzE0OWFmYmY0Yzg5OTZmYjkyNDI3YWU0MWU0NjQ5YjkzNGNhNDk1OTkxYjc4NTJiODU1IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiIzNDYzZjRmMmM4YzRhYzIxNjQ2YzhhOTAzMjQ1OWQ5NmNmZjZhN2M5YzM1OTk2OWI4ZmNjNjQ3OWU3Y2MxY2FiIn0" + }, + { + "resource": "repo://src/tui/mod.rs#L1500-L1541", + "version": "repo-lines-v1:sha256:c02fb8eb2f09e3b8ba83e5459fd21655867b92c46a0c32173b982688a345b877:eyJzZWxlY3RlZExpbmVDb3VudCI6NDIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjcwM2JkNTQ4ZDEwNWVjMGEwNjEwMDMzZWY3NjUzMDg5NTFkNDY3N2EyMTFhMTdkYjg5Y2M0NTg0YjBkNTZkOGMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE1OTM3Njc5MWU2OTQ3NmY3YTA3MTg5NmRjMjViOTEzN2VmODQyM2UzZTk2MzQwY2NmYWMzMzIzZDQyY2E1MWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImFjNDA5MGE3Zjg4ZDcxODFkNGMzMWIxMGY3NzFmY2E5NWMzMTk3YWU1NjExMzllMjE4YzU1ZTQwNWY5NTlmMmUifQ" + }, + { + "resource": "repo://src/tui/tween.rs#L11-L150", + "version": "repo-lines-v1:sha256:b631f980902f0896aa1d3ca7b1583c51f05b96343c2718f7d60538754d919d12:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQwLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI1MWQ5ODg3YTlmN2UwZTY3MDFkNzE1ZmNlYTYxNTJjMzZhYWRjMTJhYjdhZDI2NDljMzkwNTdkYjBjYmM0YTcxIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0MTJjYTM0NWNjZjc1YmY5YzA4MDZiY2U2OTViZThkZTgwOGI3OTk4NDI1MWE3YTU0ZDIwMmNmNjEwMWRkNDUxIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI1MjM1ZTI1MmZlYzgzNDI3ZjE0ODE3ZGFmY2M1YTA1ODQ0ZTExZTAzMDBmZWMzMTdhODg5ODU3MmVjZTllOWRlIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiJhNGJmZmU0YzJiMzU0YWFmNDYzMTU3ZWVmYjJkYWY5MDBmMDFmMjVkZTVjMTIzMjk4NTdiMjY4NDExZTJkNGFjIn0" + } + ] + }, + { + "id": "claim_f56e38dbc6f94f4eb4198a4336ce411f", + "statement": "Session transitions animate via buffer snapshots: entering a session slides the freshly rendered view in from the right over dashboard_snapshot (the slide is skipped entirely when no same-area snapshot exists, because blitting a stale-theme or wrong-size buffer puts foreign cells into the live buffer); leaving slides the captured session_snapshot off to the right over the dashboard already drawn beneath; and session-tab switches travel only the PTY body via blit, keeping the header session strip fixed as the visual reference.", + "evidence": [ + { + "resource": "repo://src/tui/mod.rs#L1386-L1498", + "version": "repo-lines-v1:sha256:371514eb199b3a9c9319d152b68b1a020002234ccbaaef16d0b4ae558358ec9d:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEzLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiIzMWYzMzBmOGI5OGJkMTRlYWM4MTUyZDY2NWZjMTcwOGQ0NWY4N2NlY2MxNTkzYjYzYTc5NjUzYWM3NmQ0MDUyIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0MTJjYTM0NWNjZjc1YmY5YzA4MDZiY2U2OTViZThkZTgwOGI3OTk4NDI1MWE3YTU0ZDIwMmNmNjEwMWRkNDUxIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiIxOWU3NjM1NWNmMTE5OTQ4MGMzZjk5ODM2YjE0ODZjODQxZDg5MWZhYWQ5MDRjNjA4ZTA5MzAyMmQzNzU3YWVlIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiJkZTFiZjc4OGFkYjk3ZDIzNzc4ODUyMzNjZDhiMGNkMzhmYjcyYjM2ZWNlNzgxNzI5ZTEwMWE4YTlmZWI1M2JlIn0" + }, + { + "resource": "repo://src/tui/mod.rs#L454-L482", + "version": "repo-lines-v1:sha256:bd8ab2c2d9376de0253adda02a2cd44f0460a67c3d3fe835b2550addfe13839a:eyJzZWxlY3RlZExpbmVDb3VudCI6MjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjdjNDAxZWM5NzczYzg1YjViZDFkNmVhMjZhMmQ2N2ZlMDgxMGFlMzEyY2NiM2I2ZmJhYzgzZWNiNTdjMWFmMzYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImNkYzNjNGJhZmI1NmMyNGRhZjQyODczMzZhNTc4OTIxN2VhZjY3MjNmNDcwNWE2YTAzMTIwMTQ1MTg2Yzk3ZDYifQ" + } + ] + }, + { + "id": "claim_f024b7e39480463182518a3e17dc9508", + "statement": "appearance.disable_animation (App::motion_enabled) is the single reduced-motion gate, also flipped in Settings: animation call sites jump straight to the final state, the intro splash is skipped, and App::animating() — which aggregates every in-flight slide/morph/fade/scroll chase across broadcast, tabs, popups, sessions, SFTP, pings, and header counters — returns false so the event loop never shortens its poll window to 16 ms for nothing.", + "evidence": [ + { + "resource": "repo://src/app/broadcast.rs#L16-L115", + "version": "repo-lines-v1:sha256:33cff79a62ecaaf5bb1bd6a5c4399a83759a002e6239f549b01c84c0df66c548:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAwLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiJkYzk5NWQwZmYyMmQ0YmY1MmI2YzQzNWIzMWNkNGRhYTNlNThhOGY1ZDMwMTM4MDI5NTAwMmVmNjAzMzliMGY2IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiJkMWMyZTE0ZWZlNjJkMzBjMzBlNjI1MWNiMDc3YzBhNDZhZjJiNDU1YmEwZjU4NjYzNDBmY2NhOTc5MzVlNGUxIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI1NzY1YzNmMTI3MzMzNmI3ZDFjYzdmOTY1ZjE4MTI5ZWMzMjE3YjY4OWQ4YjUzODRkMjhkZWZhN2VkYjBlNjM4IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiIzM2QxZjkyNDQwMDIxMGNmZWY4YTJiYzc1YTkxNTBlZTUxZDZkOWIwNGEyOWJmYTQ4YmNjNTYwN2QxMmIyMzQ3In0" + }, + { + "resource": "repo://src/app/mod.rs#L506-L514", + "version": "repo-lines-v1:sha256:f874af388487132bbc95705fb2a8962d2ee76999e7b3671a34b0fe6d25a457bc:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZGM5OTVkMGZmMjJkNGJmNTJiNmM0MzViMzFjZDRkYWEzZTU4YThmNWQzMDEzODAyOTUwMDJlZjYwMzM5YjBmNiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMzAxYjRmMDViNmRhMjFjODJlNDFhOGY3MjEzNGVhOTg3NjkwZjQ1YTA3NzA3ZDE1MjMyZTk2YzMyYjVkMjZjOSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiODM1OTk2ODNjYmNlZGI0NDRlN2JiM2NjNmMwZjNkOGE0ZTAzYmI0NTA5OTMwMWExNTZiNmIzZTY4OGU4MWYzYyJ9" + }, + { + "resource": "repo://src/config.rs#L13-L17", + "version": "repo-lines-v1:sha256:b7ab7b0fc47b6fe3078778c698e7d41f59fc13c4e1fde965a9c983c70c2314be:eyJzZWxlY3RlZExpbmVDb3VudCI6NSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiN2NjYzUzOGY0YzA0MTAzNzRiMzgzZDQ2MDhhNjUwYWY2OTY2ZDgxM2FiMDc0MjQxYjRlM2VkZDFiNzI2ZDZjMyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMTg4YWIwNGQ2Y2I4OGFmN2Q3ZjlkMjIwZmNjZWZlY2Y1ODQ0MzQyYmVjNTc0NThkNmI1NTlmZTdkMzUwZTYwYSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMWU3ZmNiYzg1MGM4YmIzNzNmNzNlMjQ4M2JhYTBiOGI2YTE4YjRkNzYwNDQwMjVkNmYxN2QxN2RjMGFlOTBhNyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiY2JiNTViNTBlMzY2M2NlOTM3ZWZkYmVkZTE4YWUyNjRiZTA4Y2E2ZjUxZjk5OTc2ODY0NTdmNjQzYWM1NzhkZSJ9" + }, + { + "resource": "repo://src/lib.rs#L450-L457", + "version": "repo-lines-v1:sha256:049776f7f55e17f11f452e2c338dcc52fc75da0bb49e8d03c686bf9de086ee76:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiOTg0NzFhMmI2MjlmMDhjMGU5N2VmZjdhMTcyNzgwNWM1OTI3NjYwNTdiNjIyMjA0MTI3ZGNjYTAwZThjYTFiMiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYWQ5ZTEyNTQ2NDI1NTc2ZDNkZWFlOGM5YjlkYmYxMjhkNWQ2ZGRhZjVkN2ZlODU3OTM0OTI3ZGVmZmEzYjlkZCIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMzVmNmY0NjZlNTljZmMzMDcwM2M0NWY3MmFmNjVkZTU0YWMzNjNkMzU0Y2MxYTMzMzAzNTBhY2Q0NTI0MzRjNCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiOWZmOTgzNDQ0NWU4YmZkOGE5ZTBkOTAwNTc5ZGYxODc1ODRkYTE0ZGVhM2JiOWVhNWY0YjkzMDBmMDMwOTg5MiJ9" + } + ] + }, + { + "id": "claim_844b0518d02247f7870ad875a0dc1a95", + "statement": "The startup intro animation (src/tui/animation.rs, ~10 s timeline) runs in run_animation with a 33 ms poll loop, exits on Enter/Space/Esc/q, is skipped under auto-quit or appearance.disable_animation, and is not replayed when the profile picker session already played it before the dashboard.", + "evidence": [ + { + "resource": "repo://src/lib.rs#L240-L290", + "version": "repo-lines-v1:sha256:e54a700dc1b0bce6f8915dd1897cd2547126dd3cf256e82844e62df6d77733a5:eyJzZWxlY3RlZExpbmVDb3VudCI6NTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNiZWZhNGJkNzVmOTliNmYyNThlZGZlYTA3MGJlZTRjOTc3ZTk2Njg5NjM5Njg5NDYyNmZiZjBjMjJmZGQ4YTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImUzOTMyOGUwMDM3N2JiYWU3Y2Q4YjRiOGI1OTljYmRjOGFjZmNlMmQ5N2QzYzA3NmRkMzY3NWRlZjdmOGFlNTYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhhMTBkODc2ZDQxY2EwZjRlMGMwMTJkYzQ2OWQyN2M3OGMzZTI2NTA2ZmZjYTBhOTg2NjcwOGJjMmI0MmRlYjgifQ" + }, + { + "resource": "repo://src/tui/animation.rs#L9-L9", + "version": "repo-lines-v1:sha256:ee1e7d8d7f3820f51be134e6f371c47d9f7212edc74242485cc63a1d90b82793:eyJzZWxlY3RlZExpbmVDb3VudCI6MSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZWUxZTdkOGQ3ZjM4MjBmNTFiZTEzNGU2ZjM3MWM0N2Q5ZjcyMTJlZGM3NDI0MjQ4NWNjNjNhMWQ5MGI4Mjc5MyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZWUxZTdkOGQ3ZjM4MjBmNTFiZTEzNGU2ZjM3MWM0N2Q5ZjcyMTJlZGM3NDI0MjQ4NWNjNjNhMWQ5MGI4Mjc5MyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMjc3ZjlmOWY2ZmUyMjI3Yjc2NDBiMGE3YTNlYjg2MmNiYTNjYTNkOGY1MTU5ZjlkZDhkNjg2MjcxZGZmY2ExMyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiODRhY2NiYjI5NDY0OGFmNDNkMjc5MjAzNTc2MzhlNmJiZWU4ODBmZWExNjFlZDMyNDg1NDg5YWQ3OTYzMGM5MiJ9" + } + ] + }, + { + "id": "claim_9d5aedc905ad4265b68d36c85adf7ea7", + "statement": "Background worker catalogue: config file watcher (notify recommended watcher + 300 ms debounce thread, drained via watcher_rx -> reload_hosts); ping worker (one thread, sequential `ping -c 1 -W 1` pass every 30 s, drained via ping_rx); SFTP worker per connection (right pane sftp_rx and a second instance sftp_rx2 only while the left pane browses another server); OS-detect worker (single thread blocking on OsDetectCmd, os_detect_rx); broadcast pool of min(concurrency, tasks) worker threads with DEFAULT_CONCURRENCY 8 over a shared preloaded task queue (drained by tick_broadcast); and per embedded session three PTY threads — reader, stderr siphon, and bounded-queue writer. Tunnels are the exception: TunnelManager owns child processes but is ticked synchronously each frame via check_health/try_wait, not a thread pool.", + "evidence": [ + { + "resource": "repo://src/broadcast/mod.rs#L26-L332", + "version": "repo-lines-v1:sha256:4cf2d4b46433da2bdcab08365c8f5ddc4f34b4aa3adcde1c2c16024b44b6258e:eyJzZWxlY3RlZExpbmVDb3VudCI6MzA3LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI3ODYxOWZmYWMxZWVhZDdmZTU0ZjhiY2FlYWIwZjE3Nzg5OGZiNTMwM2EyNWY3NmUzYmI0MmQ1MTU4MWUyYWY4IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0MTJjYTM0NWNjZjc1YmY5YzA4MDZiY2U2OTViZThkZTgwOGI3OTk4NDI1MWE3YTU0ZDIwMmNmNjEwMWRkNDUxIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJhYzc4NDdiNzVlMTJiOGQ5ZmMyM2QwZTczNDQwNTE5MzRkMThiYWUyZTJjMzk3OWQ0MzQ2NDExYjg3OTk5YjFhIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiJmNzYzOGM5OWRkZDk5MDMyZTdiNWFiNDY0ODYzNjcyYzA0ZGJlYmI3ZGQ2ZmQ0Y2ZiMDhlYjQyYzhlNDQwM2VkIn0" + }, + { + "resource": "repo://src/osinfo/detect.rs#L104-L119", + "version": "repo-lines-v1:sha256:db560a5e44699b3d29f7cbc9c2f912de09eb7996dae8e36520ae192f720042c6:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjhlYjBjMWY1MDMyNWMwNzc4NDgyNTk2YWUyOWU4NTFiYmMzODIxOWI1MjJkMDJjMWZlYjkyOWQ4NTBlZjAzZWQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE4OGM0MzJlOTNiYThkMWUzMDA5ZTgzZDE1ZjQ0Y2ZkYTgxNjMyNTBlMTdlYjNiNjEwOGYzMjBmNDM5ODViODYifQ" + }, + { + "resource": "repo://src/ping.rs#L40-L60", + "version": "repo-lines-v1:sha256:67dcf67966f85202ba4027506ac44577bdb7bb4cd349454bb0d8d3c59a675adf:eyJzZWxlY3RlZExpbmVDb3VudCI6MjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFkYjcyZmQwNGQxNTdjNmM3NDI2MWMzMzYzZmI3NThkNTNiMmIxNmM5ZWI0MzdlNmFmZWUyN2UwZTdmMTI0MzYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNlNTRlOTFjMTc3M2JmM2ZlMGUwM2QzYzZkZTZhMjVmNGZjNDZhOWY1OTM4ZWQ5YzJlOGU5NGE0ZWMwMTgxZTQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjUwZGE0MmY5MjgxMWEzNzRiNTkwMDVlMzg1OWVmMWE4MDNjN2Y5NDFlNDdjMDcwMmJkZjI3OGU1NmQ2NGQ0MTUifQ" + }, + { + "resource": "repo://src/session/pty.rs#L188-L269", + "version": "repo-lines-v1:sha256:d54faad1f1af8e404839904e76cffdb91e107dd4ec65c60c24b718ef8bc3e1ae:eyJzZWxlY3RlZExpbmVDb3VudCI6ODIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU5MWMyOTk1NjMyY2Y1OGRlMGRkMTQ5YzZlOGE4YzE4ZDJmZTFjNDk2YWZjMjVjMmRjYjc0YWIxNjE5NTBkYjAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU5YzEzNTkwNjY1NjMyM2QxYzVlYTQxNjU0ZWU2NmFmYjU4NWFjZGNmYWVhY2ExMjFkNWUzMGJmMTkzZWVkZDciLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjU5ZDI5NDY4MTFiNjk2M2ZlNjAyNjQyMWYxMDAzN2RjZmQyODcwNzhhMWRjYTIyNzIyMTgxNDM3OGRkZTlkODIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImJkODU1OTljODUzNmJlYTY4ZDBkMTg5Y2IyNDY1YjAyYzZmNDk4ZWUyZGYyNjFjNzQ4YmEwNTkyZWE1M2EyMjUifQ" + }, + { + "resource": "repo://src/sftp/worker.rs#L1-L96", + "version": "repo-lines-v1:sha256:77182f43b9b4b5898c7f029e3640fa6ff999a0e5ad4a6f317420445b5a3d0839:eyJzZWxlY3RlZExpbmVDb3VudCI6OTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjVjYzU2NmQzNDQyODE2ZWI5NjA3NTY4YmY0MDRjNjEwNzM4ODQ3ZGIwMDUxYmU3OWE3ZDc0MjJlNzhjMDNlODQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImYzOGFmNTk3NzRkZmJkYWM1N2I1Y2JiNzIzMGFmNTQxOGEyNzQ2MDNiNDY0ZDI2ZTM1YjBmYWY4NmYwNGEyNGEifQ" + }, + { + "resource": "repo://src/watcher.rs#L17-L51", + "version": "repo-lines-v1:sha256:ce5247072d9f74dfe5ad873e65e6c56ade1524d52c7995703a62294b2dbd3aaa:eyJzZWxlY3RlZExpbmVDb3VudCI6MzUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRiZmNkMTkzOWY5OTZmZGY4NTI2NjU0ZTM1YjJhYzgyZDM3YTYxMWYxZWY3MjM0MGFlMWM3Nzk4YmYwNjZiZDEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjZmZDM2Zjk5MTY5ZmVmYTZhZGU1NGFkMzVhNjY1YjU1ZDI5OWUwOTJmZTM2ZjEyYjlmMjdhYjE4NTkzMGFjNTkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjY3OGFmN2RkMjJlMDYxZDVhODQ0NDJlNzI4ZWQ2ZmIzNDk0MmE5OGUzNDAxYzIzZmM3MWM2NGE4ODM3ZWNjMDIifQ" + } + ] + }, + { + "id": "claim_bca61dd311204af7af294710013fc669", + "statement": "Workers self-terminate by channel drop: the ping, SFTP, and OS-detect threads end on a send failure or a closed command channel. Restarting the ping worker after reload_hosts drops the previous Receiver, winding the old thread down on its next send instead of leaving it pinging deleted addresses; the SFTP worker connects once then services commands (ListDir, RunQueue, Remove, Mkdir, Rename, Chmod, Cancel) until the channel closes, and progress events are throttled to one per 64 KiB.", + "evidence": [ + { + "resource": "repo://src/app/mod.rs#L1112-L1124", + "version": "repo-lines-v1:sha256:157be1252ca5e586a6b5ae8a48cb584bc35aeca27b74bb21446d98fb9feec004:eyJzZWxlY3RlZExpbmVDb3VudCI6MTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkzOTZjNmQ2MjMzMDE1ZmVmMDMzNmI2ZDNhN2JkOGY5MGJmNjU3YTljOWFkOGE3ZjBiZWMxZjk1MzJmMWI3YWMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjZiYmQ1NjU1ODlmNTdkNzhiYTVjZDVkZWQ2Mjc4MmM3Yjg4ZjMzYTZhMTc5ZGNhMWFmOTZlM2FjZTQ4OTgxZmEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImY1YjlmZmE3ZTU2MGU5NWYwZDM0MDQzMzMyYmEwMDhmYjE5NzRiNDE5NTQ1NmI4NjMxYTFiNzMyYjJiZmU1YjUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE5YmUxZWRiNTBmYWYwMTcxNzgwOWRjMTQzN2MyODM0ZDQ1NzdjYmY4MTYzOTM1Y2MxNjUxMjU0NjQ1YzRiMjQifQ" + }, + { + "resource": "repo://src/app/mod.rs#L1171-L1174", + "version": "repo-lines-v1:sha256:21ad3dadb26e78140567003a665fd7a51371ca3e803b5da3e3566ac39804e658:eyJzZWxlY3RlZExpbmVDb3VudCI6NCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNzA2OGM3YTA5NTQ5MGUwOWJiNDNmOTFhZTI3MDYyYjgzYjY2ZTdhMzVlMDQ3MGJiZTIzMTJkMjE3NmFmMDBjOCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZTZiMDQ0OTE0Njk3MjJlODM2M2E5NGU0MDQ0NTM4ZTAxM2EyYTIxN2UzNmIxZTU2OGJhNjBhY2I3YjRjOGYyZSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNjgzMzY4OThkZTI1NjQ1NmIzOTgzMzg1NjgyMDQyYjEyYjE4Y2JiOGI3ZDY1NWFkYWUwMzAyMmY0ZGYzODAxZSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMTYyZWU1N2U0MWE4YzIwYWUwMTY0ZGYyNjE0MzlkY2M0OTlkMzUyMDgzNDE1MTUxNmQwNDdlOGRlYWU4YTQ2NiJ9" + }, + { + "resource": "repo://src/osinfo/detect.rs#L121-L130", + "version": "repo-lines-v1:sha256:9cb5b24e3e988204a62ab146d67415dd70175dcff109eef8b7bd3372dbe4677b:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjljNmNiNjIxMzdjYWVhMGY2YjIzZGMwZTc2NGM4NGNmZjQwNzhlYTY5YWI2MTk3MThmZjFlMDEzMDdjZjBhNDciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRlMGFlNzU5Y2I1YzYyZjkzZWNkMDE0NjcyMWU2M2NlYTMxMjcxMzY0ODVjNGZjN2VmMDZkYTAxYjlkNzA5NjQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjU2ZWY0ZTM5NzUwOGNhOTgyZTIwYjhiNGUxZTAxZjA1Yjg4M2EzYTE0YjU2NDY2NWVkZjNmZTIyMzMyN2Q1ZDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjdmNDY2MGEwYjdkNTJkNTMyMTNlNzdkZjI1YmU1NWUwMTI4OGZiNzNlNTExOGU2MzVmZWQ1Yjg2N2FjNTY3NDEifQ" + }, + { + "resource": "repo://src/ping.rs#L42-L60", + "version": "repo-lines-v1:sha256:27afd4a410791d8f119bf7c34ece36911417c58779c1a2dded54eb456ffffbbb:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU4MDgzZDMxYWQ3ZmRjMGI4MjY0NzRmODlmZTdjZDBmMjZhZmU0YjQ0ZDRlODVlOTNmOWUyM2FlZTRjYjZiMDAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImNjNzVkMWFlYzQ2OGQ0NDFjMjExYmRlYWQ3ZGYwOWYyOGYzNjM2YWQ1NjJkMzlmYTgxNTVhOTA3ZjY2M2UwZmIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjUwZGE0MmY5MjgxMWEzNzRiNTkwMDVlMzg1OWVmMWE4MDNjN2Y5NDFlNDdjMDcwMmJkZjI3OGU1NmQ2NGQ0MTUifQ" + }, + { + "resource": "repo://src/sftp/worker.rs#L70-L96", + "version": "repo-lines-v1:sha256:0fdccb1833ae09c600a0cec475f3d01608bb561e6a87383a9143416becca6181:eyJzZWxlY3RlZExpbmVDb3VudCI6MjcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI2YjhlMWIxMzM1NzBjOTlkOGNlZTE0ZGEzMTc3NzM1OTZmNjkwY2FhMDdhNjBmY2U2YzgzMDU2NDFlNzllZGYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijc1MjlkY2UyYTNkYTllM2I2MjZjZjI4MmMzMzQ2N2IwYzNiN2M0N2Q2NTA1ZTFjNGIwZGNhZjQyYzdmZWIwOGIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImYzOGFmNTk3NzRkZmJkYWM1N2I1Y2JiNzIzMGFmNTQxOGEyNzQ2MDNiNDY0ZDI2ZTM1YjBmYWY4NmYwNGEyNGEifQ" + } + ] + }, + { + "id": "claim_918b1df1eb3f4fea8ca464d2ddd77690", + "statement": "The OS-detect worker is silent on failure: an unrecognised or failed probe leaves the host's os_icon empty and the probe is retried on the next connect. Probes are kicked off only for managed hosts whose os_icon is still empty, and the os_detect_inflight set prevents duplicate probes for one host; apply_os_detect persists the result and reloads hosts.", + "evidence": [ + { + "resource": "repo://src/app/connect.rs#L216-L258", + "version": "repo-lines-v1:sha256:fffee6e0bc82f78b10ff4cff2dd13f32c404104176f77368d58b17eaa93a5b9e:eyJzZWxlY3RlZExpbmVDb3VudCI6NDMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU3NGQ1MjkyOGI5MTBiZDI2OTRhZWIxZmFhYTU5NTA0NDdiYmJjZjVkN2QwNzUwMzRhNWFhMzYzOTVhMmE3ZTIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQyMzRiODNhZmYyNmNmMjUxYzg5NmY1M2ZiMTgyODcyZmQxODU2ZDg1OGRhZmE2Y2QxMDUzOTM0ZWY2Nzg5NjkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEifQ" + }, + { + "resource": "repo://src/osinfo/detect.rs#L1-L10", + "version": "repo-lines-v1:sha256:6a6cb298861ef6a824c4c3536d97aca5c9b65b8b381bdf2969b920afe5b75037:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjJkNDI4Y2RlMmUzZjUwYjQ0Yjk5ZDVlN2E5ZmI3MTBkZGUwNGNhNTBmNTE5YjEyMGU5ZTRmMGNiNGMwNDdhNTciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImNhMTMwYzJlNjE2ZmNmNTFlNmY3MDI0MTUzNDY4MWU2ZmQ0YmQ1NzdiY2QwMDc1ZWZiYTZkYjRkNDViNTY4MzAifQ" + }, + { + "resource": "repo://src/osinfo/detect.rs#L121-L130", + "version": "repo-lines-v1:sha256:9cb5b24e3e988204a62ab146d67415dd70175dcff109eef8b7bd3372dbe4677b:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjljNmNiNjIxMzdjYWVhMGY2YjIzZGMwZTc2NGM4NGNmZjQwNzhlYTY5YWI2MTk3MThmZjFlMDEzMDdjZjBhNDciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRlMGFlNzU5Y2I1YzYyZjkzZWNkMDE0NjcyMWU2M2NlYTMxMjcxMzY0ODVjNGZjN2VmMDZkYTAxYjlkNzA5NjQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjU2ZWY0ZTM5NzUwOGNhOTgyZTIwYjhiNGUxZTAxZjA1Yjg4M2EzYTE0YjU2NDY2NWVkZjNmZTIyMzMyN2Q1ZDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjdmNDY2MGEwYjdkNTJkNTMyMTNlNzdkZjI1YmU1NWUwMTI4OGZiNzNlNTExOGU2MzVmZWQ1Yjg2N2FjNTY3NDEifQ" + } + ] + }, + { + "id": "claim_325fba4542a545c29516775fe550f00a", + "statement": "The broadcast pool guarantees exactly one terminal event (Finished or Failed) per task, including under cancel — un-started tasks emit Failed{reason:\"cancelled\"} and in-flight children are killed. Workers pull tasks from a preloaded channel whose sender is dropped (shared behind a Mutex so recv never blocks); the real SshCommandRunner shells ssh non-interactively (BatchMode splice, ConnectTimeout=8, StrictHostKeyChecking=accept-new) with a 60 s run cap and an AtomicBool cancel checked on a 50 ms try_wait poll.", + "evidence": [ + { + "resource": "repo://src/broadcast/mod.rs#L286-L350", + "version": "repo-lines-v1:sha256:54ee5bcdd69e977a3f817a2b8f0fcfa3c52b25ff5bd4b4b9ca2574d1fc973a71:eyJzZWxlY3RlZExpbmVDb3VudCI6NjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjViZWI2ZjY2MTMxMjU5ZmI3MGRiMDU5ZWZjYmY1MTI3NGJlNTcwMGM0ODY1NzI1MGU2OWIzNzZkOTU5MGYyNDIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRhNGRiNWM3OWJjMGYwOWY4MDUxN2I0MzBkNDU5NjJmOGI2NzNjYzYwZWY4MDYwOWEwMzBiOTI0YmFjMjNlYTYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQ4ODU5MjEwZWJjYjEyY2Y2OGM2MTYyMjJmYzU2MGMxZjY4ZTJmYTMxZjYzMDQ5M2RmODA5NTg4ODdkNTdhZjkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImNkMjI1ZmEwZWUxZTIwOGZiYjIyNmM4MjQwODkxNDA1YjU2MDE4MTA0YzNkOWU3YzI4ZjA0MDM5MTlkOWM0NjgifQ" + }, + { + "resource": "repo://src/broadcast/mod.rs#L82-L115", + "version": "repo-lines-v1:sha256:811d7b6634863f1656f9e37ca75a18f5b524f670677c074f22fef5516d9f5718:eyJzZWxlY3RlZExpbmVDb3VudCI6MzQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA0Njk0NDhiYmYzMTgyYTI0NjFhNzFjM2Y0YzM0ZmVhYzIyMDZmYzRiNWZhZjk2ZTA5YWYxMDY1Y2M0NTBiOTEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNlOWQ1ODEyYzE4MGQ3NmFmMmU1MGRiYWI2Yjg1MTcyZjg4MjZiZDhkZjNjMzEyZmE5OWM0MWFlNzZlMDU4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJhMGU3MmFiNTVjNDNlZmJkYmM3NGExMTk4NGE1MmE3YWJhM2UyNjQ2YzljMGI5YzdhMTExNjFlODU0Y2I4NzMifQ" + } + ] + }, + { + "id": "claim_a3c64dc5b20b4d63b442984e5ca81b9c", + "statement": "PTY writes go through a dedicated writer thread behind a sync_channel bounded at WRITE_QUEUE_LEN = 64 — the 0.15.1 fix for the frame loop freezing: write_all on the PTY master blocks once the child stops reading its stdin, which parked every tab when it happened on the frame loop. All writes toward the child (keystrokes, auto-typed secret, pastes, emulator query answers) keep their order through the queue; PtyRuntime::write uses try_send so a full queue is an immediate 'the remote is not reading' error. Drop closes the channel but deliberately does not join the writer, because joining would move a blocked write's freeze into session teardown.", + "evidence": [ + { + "resource": "repo://src/session/pty.rs#L188-L203", + "version": "repo-lines-v1:sha256:7690a770dbd69f6c35d3aa8fa8796b2b584f4bc27f4ccce923a79fa10da1aa28:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU5MWMyOTk1NjMyY2Y1OGRlMGRkMTQ5YzZlOGE4YzE4ZDJmZTFjNDk2YWZjMjVjMmRjYjc0YWIxNjE5NTBkYjAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImJlMTg5NzBmZjI2YTkwNTk1MzI4YmI4ZDVjOWM0YWE1NjU0Yjg5OGM5NTgwMTY1ZDFiZDIyNGU5OTkwYjY4ZDkiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjU5ZDI5NDY4MTFiNjk2M2ZlNjAyNjQyMWYxMDAzN2RjZmQyODcwNzhhMWRjYTIyNzIyMTgxNDM3OGRkZTlkODIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhjNjY3Mzc2ZWMyODhmNThkOThjMzUwMWJjMGM4NmViNDRkMzRmMmRkMDIxMjE4NzY1MzZlN2ZjMmFiNmVhYzgifQ" + }, + { + "resource": "repo://src/session/pty.rs#L21-L32", + "version": "repo-lines-v1:sha256:346ad20d0ff54f14b245c7fabcd0225396d2977b7c9fb2ba9d70b207d4497420:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM1MmVkMWQyN2QxNTdkYWNjZjExYTQ1YmRkMjkyNzk0YjBlNmMyYmJmNGFlOTk2ZDU0Y2I2ZmFkNGZjOWM2M2EiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU3MWQyZThiZWE5ZTcwODk0Y2I5ZmY3ZjFlMGMzN2M2Njg2OThlNWNhYWI0ODBmOTQzODcwYTI1MjgwMWVmNzkiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE1Y2VkMjE4OTFlZTZjMjcyODlkOGQzZmRlYWJiZGViYTU0YzFmZjkzZWI4OTVhY2I0YmNiY2Y5OTlhMjBkYzAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijg4ZTNhMTdlZGM2MjFiZmFlM2RjMDllYTliZTFiYmMzYzgyNjU3NWViN2ExNWZlZGViNDBjZjk0YzMwNmQ1MGQifQ" + }, + { + "resource": "repo://src/session/pty.rs#L289-L306", + "version": "repo-lines-v1:sha256:1f1d2e8b834c3e1b891252182f86b3bc229416835eab56c5c685236763c2a5cc:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVlYmE1ZmMwOTJhNTNkMjBjNDZhNWFjYWY5ODE2MDYyYjUyYTBhODE3M2JiYTM3ODExZjUzZTM2NzA3MGE1MjEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNiZjI0NWI2MTAyMTk5YjkxZTI2MmI5MjA1NTg4ZmY0YjMwMzgyYzViOWI0N2VkZWFmZTA0ODlkZDQ1Y2U5YWIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImU1MTA1Mzc4YjE3NWM5ZTdkMjA4MjJiMzhjY2I4Nzc3ZTM1NzQ0OWE0MGJiNmYyODM5MmFlYThkODJkMmY4NTQifQ" + }, + { + "resource": "repo://src/session/pty.rs#L361-L378", + "version": "repo-lines-v1:sha256:8b9931052eac117a026ba04093a34dbb62b94b0cf64e9d8c7a99aa6f46e55d72:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImJiNzllMWNjNDUxOGZjZWEyMzA5MGMyN2M4YWMxZDNmZWExNDk0MGFmYjAyY2IzOWQwYmY1YTdkZjg0YzYyYTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjgyNzgzZjhhY2Q1MjZiOWJhNzI2Mjk2Y2ViZDVjMDkzOTFhYTA0OThhMGFkY2M0NzEwYTEwYjI4NTA0N2Q5NGYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ" + } + ] + }, + { + "id": "claim_7634e5fff46a43d5ac329819145bd162", + "statement": "The child's stderr is siphoned through a mkfifo side channel (0600, opened O_RDWR|O_NONBLOCK so an empty FIFO yields EAGAIN rather than a premature EOF) via an `sh -c 'exec \"$@\" 2>\"$SSHUB_STDERR_FIFO\"'` wrapper that keeps ssh's PID for signal delivery — so `ssh -v` handshake output lands in the session's debug_log (connect spinner, host-key fingerprint scan) instead of the render grid, with a 20 ms poll non-blocking stderr reader honouring a stop flag. If the FIFO cannot be set up the connect degrades to the plain PTY rather than failing.", + "evidence": [ + { + "resource": "repo://src/session/pty.rs#L127-L165", + "version": "repo-lines-v1:sha256:19f5fdefd06b5c06a5a0dba8f8b582029c89c3ecc4df2404fdb736747c2bec4a:eyJzZWxlY3RlZExpbmVDb3VudCI6MzksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM5ODMwNWU4ZDBjNzIzYmYwZTg4NGYyOWNmODcyMGNlNDRlNGVhNWEzOTU4MjVkMzJkNzI3NzA3ZjZlMTFlNDkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImM5YmYwODFjYTQxMDBhYWRiMGY5YWYwY2MwN2E5ZWQyMzNkMjhlZGM1ZGRmNDllYTU4YTcwOGQzOWQ4ZWNmYWQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijg4MWE4MWNlZTRmNTUwZTM1OTEyZjhiZGRhMjY5MzBjOTNkZDhmZTJkYzliMjEzM2FlOGVkYzk0ODhiYTA0MDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImJmMTBiMzU1MWVkZjY5NWRmODM3Y2Y3MzE5MTUyMTljM2VlN2M1ODVhZDRkZmMzZTM1NzA0MmY0NzZlOGNhYTQifQ" + }, + { + "resource": "repo://src/session/pty.rs#L236-L269", + "version": "repo-lines-v1:sha256:f574bbdb3913888f77de141b99b183b1bd4d515e6e8b1e4e6d00e5d98a0621df:eyJzZWxlY3RlZExpbmVDb3VudCI6MzQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjJkNmM5NGE4NmY4MjU3YTNhOTI2MzQ0MDdiMjJhMjQxMjI1NGYxOWQzODZiMDQ5MzRiZGRiOTU5YzZlOGFlZDUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU5YzEzNTkwNjY1NjMyM2QxYzVlYTQxNjU0ZWU2NmFmYjU4NWFjZGNmYWVhY2ExMjFkNWUzMGJmMTkzZWVkZDciLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNmZmI0ZDIzMjhhMmJlY2Y3YmY3MDk3NzMyMGRkMzBjY2EzNjk1NzBmM2U4YWNlNjRiNjRkZTJkNWI3NmJkOGQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImJkODU1OTljODUzNmJlYTY4ZDBkMTg5Y2IyNDY1YjAyYzZmNDk4ZWUyZGYyNjFjNzQ4YmEwNTkyZWE1M2EyMjUifQ" + }, + { + "resource": "repo://src/session/pty.rs#L52-L100", + "version": "repo-lines-v1:sha256:17b8c30222517dcd185a57ec015d6aa7791107bfd7d4d65b315acefd68c2735a:eyJzZWxlY3RlZExpbmVDb3VudCI6NDksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM4MzE0MTdhOGJjNjc4MTQxMWQ4ZjdjZDM5YmE5ODhhMTdmYzRiMzM5YTVkZTQ0ZDY0NWRlMDI3MzE0ZGE5MjYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjAzZjU4YjAyMGM0NTRlMmU3OThhOTQ0NDI2YjhmNTgyMjVmMDRlMDlkNzJjOTVhY2FlOTUwNWI0ZGJjNWE5ZmYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk2MDY4M2E2MDdkYjliYTljNjRkNjc4NTdlMDU1Njg0YzUwNDM0YTU0ZGExZjQ4NzY2OWI2MmQ3OWUwN2IzMDcifQ" + } + ] + }, + { + "id": "claim_c5f31e7a4ef34b85bd2f8e8378598882", + "statement": "Session::drain is called every frame for every session: it non-blockingly pulls PtyEvent::{Bytes, Stderr, Exited} from the PTY reader channel, feeds the vt100 parser and the optional session log, accumulates capped stderr debug output and scans it for the host-key fingerprint, reap-exits into SessionPhase::Exited with the child's real status, and after bytes land auto-types the pending secret exactly once and answers terminal status queries (rate-limited by a reply-token budget) so a wedged remote cannot crowd out keystrokes.", + "evidence": [ + { + "resource": "repo://src/session/mod.rs#L511-L594", + "version": "repo-lines-v1:sha256:0d656c78440a330b41ea5079f924875da3e1d8d368712f52ab1d66dbf6bf1e32:eyJzZWxlY3RlZExpbmVDb3VudCI6ODQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijg4Yjg0NjRkYjAxNjk0MTBkNjZjMjM1MGZhODEzNGM1ZDg0ZGM3ZGEwMzgyNTg4MjE4ZmYzNGU5MWQwOWNmZTAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQ1YjczODIxYTg5ZDhjZDhhZTQwNTk1Zjk2MTFjZTAzNjg0YmRhNjZhZTY1NTI2NWU2YjM2ZjAxYmFlY2M1NzQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjNiMGU5NWI2MDNkYmIwYTc2YzIyN2U0MzFiOTE4NjE1Y2VjZTcwODhlYzU0OTRjYTY2OTY0NDRiYmJlMWE0MmQifQ" + }, + { + "resource": "repo://src/session/mod.rs#L910-L934", + "version": "repo-lines-v1:sha256:3b72757045af52e72bedeb9b09a98fc1db285cfc2ee8398f0c453d974149f3bb:eyJzZWxlY3RlZExpbmVDb3VudCI6MjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2ODA2Y2M0ODU5NDkyMGM5ZTAzZTg5NGVhZWM2MGNmZGY4NDhjZjljOGU3ZTdlODNiNGNkOTcyMjllMTNlYzUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImU5OWYyNWNmOTVlMzNhYWQwODEwODI2NWI3ZDQ1ZThiZWE4ZTMwNTI5NDJmNzRkMTI1NzY2Yzc1MDY5NWQ5NGYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI1MGViY2FjZDRlNDY2ZDQ2MTQ0MWQ0ZWYyYzFmYWZkYjhjMmIzYzFhYjc4NDBkZmQ0YWIyYzE5ZjA4N2M5YTgifQ" + } + ] + }, + { + "id": "claim_a5555655600b4e6e8862f32641232a22", + "statement": "OSC 52 clipboard writes requested by applications inside a PTY are relayed only for the session the user is looking at, and only while clipboard.relay_from_pty is on; background sessions discard requests immediately rather than queueing them for later. The relay runs before diagnostics are collected in the frame so a failed relay still reaches the SSH log this frame.", + "evidence": [ + { + "resource": "repo://src/app/session.rs#L254-L285", + "version": "repo-lines-v1:sha256:88f419923551a418526f176eaa56920972b0a222ffcc6e1580ead8cca8d938e0:eyJzZWxlY3RlZExpbmVDb3VudCI6MzIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjViMTNkNjcyZWNmYWQ2MDM1ZmYxODE4OWU0ZWE4OTQwOTEyMTk0YmRlZjA0MGRhZGU3YTc5ZjJlYTM5ZWVmMWMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjcwMmNjNzYzOTUxNzJlNDkwZGM2YjlhMWNhZGI0NmRmZjk1ZmZlZGYzNzlkZTRmZGU2YjNkMTdhYzY3ODJiYzYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjI1NzY2ZjAzZDkyMTc1OTczNjNiNmJiMzk2NmRmYWM5OTUyZjNhOGM4ODBiZjYyZTRjNGQ4MmY0MTIwZGIxMTgifQ" + }, + { + "resource": "repo://src/lib.rs#L331-L347", + "version": "repo-lines-v1:sha256:528e3df50b2eb97cfb476019657ebf7fbae38627db2460f87e59cf0423155960:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImViMjhjZmY4ZjMxOGJhY2Y3MGM0NzM4MjBmNWIwNDdiZjk0NDUyYWU5ZTQ3YzYyYjRmNjc5M2ZlYjU4ZTg1NTgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRhOWY4NmQyOGU3ODRmOWZkMzFmYjcyZDE1ZjYwY2JjZWFhZmRkNjY5Y2JkNWI2ZGE3ODg4YTAyMzMyMWViYjkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImE5MmMzY2FlM2UyMWJiMDVmMzJlNTVlYzBhYTgyNjhmYzg4MzA3NzFlMmNmOTdkZGU0NjgxNDY0YTk1NzdmZTMifQ" + } + ] + }, + { + "id": "claim_198dc5d8ae4442329e76d5f8cf5eb3af", + "statement": "Quitting calls App::shutdown_all, which clears the session list; dropping each Session drops its PtyRuntime, which kills the child's whole process group (SIGHUP then SIGTERM escalation, then kill/wait) and joins the reader and stderr threads, while the writer thread is deliberately not joined.", + "evidence": [ + { + "resource": "repo://src/app/session.rs#L444-L449", + "version": "repo-lines-v1:sha256:24dec9a0fb8070bd532096eb42089e934cbd5ea4fef6522d65bd5bdf977f49a1:eyJzZWxlY3RlZExpbmVDb3VudCI6NiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYzcwNjE5NDY2NWFkYjk2YjA0MDM1NmFiNWY0ZGE2MWE0NjFjNjc0ZDA2ZTNhMWY3N2VlMjA1ZmU5ZjFmYzU2ZSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMjM1NzYyZTQxZWE3N2NlMGRhYjFiZTEwZGM5Mjc4NDZhNGExYTBjOTY2NWJkZTkyZjhhMDNlMjUxM2JhMGNjYyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMjUwMDQxMmZjZWU2NGQ2OTUwZTdiNTMxN2M5MTg5NTE0ZGQ4NjA5YTI1MzA2Njg1MzUxYTYzNWRjZmE5ZTBmNSJ9" + }, + { + "resource": "repo://src/session/pty.rs#L334-L359", + "version": "repo-lines-v1:sha256:9db4f30b3d7bcbbbf97245c07aaddae6cf0cb4c51791575b74dcbeec89c2e12e:eyJzZWxlY3RlZExpbmVDb3VudCI6MjYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNmOWVjZjI4NmRlZDQwZTZlYmZhODdjNTRmZTU0NmMwYzc2NzM4NjdmNWEwMjE1NWEzZDc2ZjVmNTQ1Y2E4MjAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjljZGYwNWE2ZjQ2YWU0ZmExZWE1YjJhZDc1YTE3ZTRlYjAwZmVkZTk3ZGEwZDlhOTFlNDJjZTU4NTY3MGRjNWYifQ" + }, + { + "resource": "repo://src/session/pty.rs#L361-L378", + "version": "repo-lines-v1:sha256:8b9931052eac117a026ba04093a34dbb62b94b0cf64e9d8c7a99aa6f46e55d72:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImJiNzllMWNjNDUxOGZjZWEyMzA5MGMyN2M4YWMxZDNmZWExNDk0MGFmYjAyY2IzOWQwYmY1YTdkZjg0YzYyYTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjgyNzgzZjhhY2Q1MjZiOWJhNzI2Mjk2Y2ViZDVjMDkzOTFhYTA0OThhMGFkY2M0NzEwYTEwYjI4NTA0N2Q5NGYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ" + } + ] + }, + { + "id": "claim_9b9c251954884ec2bd5ac9478c6fa2a6", + "statement": "The file watcher monitors the ssh config's parent directory rather than the file — editor rename-saves swap the inode and silently detach a file-level watch — filters notify events down to the config file's name among Modify/Create/Remove kinds, and debounces with a fixed 300 ms window (WATCHER_DEBOUNCE): the first config-touching event opens the window, events arriving before it expires are swallowed, and exactly one WatchEvent::ConfigChanged is emitted, which the event loop collapses into one app.reload_hosts() (the hot-reload feature). The debounce loop is abstracted behind an EventSource trait so tests inject scripted events with a virtual clock, and a watcher that cannot start disables hot reload with a warning instead of blocking startup.", + "evidence": [ + { + "resource": "repo://src/lib.rs#L180-L189", + "version": "repo-lines-v1:sha256:4ae29184c4b768bda2e46c6bdd08ef937d71ce3fde2811cab885012837b2d777:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQxNzQzOGUxNmVhNDQ0YTM4ODMxNmRlODUxZGI1NjhhZDNkZGRhOWY2MWE5MGNiMTI0OGQ2ZTgxMmZhZDlmM2YiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImY3OTkyYjg5ZmIxYmMxYTZjMjA5ZTk5MDA2NzYzNGVmZTgzMjdkYzJhZDQ1NDljOTZjZjcxMDhkZTNjMzZlNWUifQ" + }, + { + "resource": "repo://src/watcher.rs#L114-L131", + "version": "repo-lines-v1:sha256:e084734c34ba175a560e4239667ff70a6fe3e8f7aa6a53d1f628af49be587c1c:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjJlYTFhMWRiMDc0ZDY0MDY4NTU5NDM4N2YwYzQ2ZmQwMTM2ZGVjMGUzYzE2MzlhYjQ5MDFlZDkwYjMxMTg4MTEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU0M2EyOGUxNzdlZjFhYzQxN2EyYjA4MWQyNmJlZTcwNTlkNGQ2NTI2YmJjMjA4YjNlY2FhMDlkMThhNWUxNzQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjI3OTk1ZjMxZTI4M2ExOWFmOTRjYTBmNTdiOGY4ODQxODQ3YWE2ODY2YjI3ZTRjNWE0NjlkOGI3MDM2ZmQwMTYifQ" + }, + { + "resource": "repo://src/watcher.rs#L17-L51", + "version": "repo-lines-v1:sha256:ce5247072d9f74dfe5ad873e65e6c56ade1524d52c7995703a62294b2dbd3aaa:eyJzZWxlY3RlZExpbmVDb3VudCI6MzUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRiZmNkMTkzOWY5OTZmZGY4NTI2NjU0ZTM1YjJhYzgyZDM3YTYxMWYxZWY3MjM0MGFlMWM3Nzk4YmYwNjZiZDEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjZmZDM2Zjk5MTY5ZmVmYTZhZGU1NGFkMzVhNjY1YjU1ZDI5OWUwOTJmZTM2ZjEyYjlmMjdhYjE4NTkzMGFjNTkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjY3OGFmN2RkMjJlMDYxZDVhODQ0NDJlNzI4ZWQ2ZmIzNDk0MmE5OGUzNDAxYzIzZmM3MWM2NGE4ODM3ZWNjMDIifQ" + }, + { + "resource": "repo://src/watcher.rs#L53-L112", + "version": "repo-lines-v1:sha256:90b1f2d15c4fde555731349db10194b646a2a86a3b8876e57d9d8d2a154f82ca:eyJzZWxlY3RlZExpbmVDb3VudCI6NjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijc4ZDA2ZGUxYWI3ZWVkYWJkNTY4ZjVhYmQxODlmYzQ5Y2Y2YjRlMWZmMTg2ZTM1YjFkYWFhM2YzZTRhYjBmZjgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjMzY2RhYmM0NGY2ZTRmNDIzZjVhNDAxMTdmYzQ4OWJiOTdjOTkwMWVjNjg2NmE1YzYwMWM3ZjZmZThjYmJkYTkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjM5MjA5MTk3MmMyODFkNzc1MGRlOWViNWNhNDBhMTM5ZTBkMWU5NGFjMzllYjRmMTFjOGQ4ZTdjZmJlY2FlYmYifQ" + } + ] + } + ], + "verification": { + "by": "openwiki/0.5.1", + "at": "2026-09-11T12:46:43.817Z" + } +} diff --git a/openwiki/.claims/changelog-coverage.json b/openwiki/.claims/changelog-coverage.json new file mode 100644 index 00000000..4c43676c --- /dev/null +++ b/openwiki/.claims/changelog-coverage.json @@ -0,0 +1,180 @@ +{ + "schemaVersion": 1, + "pageVersion": "sha256:7f0ebcef8567975f150e98d747690aceb046192f2544ede43d0e7f78f719b863", + "claims": [ + { + "id": "claim_96ad782f4b2149e084923b5c7f97874d", + "statement": "The changelog-coverage ledger audits every CHANGELOG.md entry — the (currently empty) Unreleased section and every release 0.16.0 back to 0.1.0, split into one row per distinct feature/fix/behavior change — against the source tree, and maps each row to the exact OpenWiki pages that document it with a Covered or Pending status plus a one-line reason.", + "evidence": [ + { + "resource": "repo://CHANGELOG.md#L1-L30", + "version": "repo-lines-v1:sha256:a71d6ec7d60f7ce7e3c08e850b2ada5ddb65b32858cb90b1b7b0ed6c56754b5a:eyJzZWxlY3RlZExpbmVDb3VudCI6MzAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNlNzljNGNhZmI1MDRhMjFmODkxM2U0ZTBlNjZmMmZmN2IxMTkyYTEyN2M2ZjU2NGFhYjM3OWM4YjVmYTliZGQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA2YmIxMmY4ZGI0YWY3N2U2NTBjOTljYWRmYTU3ZDg2NTZmNTkzYWQ2ODY4YWVkZjQ3ZjlkNzQ2NjUxN2NiYTgiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImFjYzQyMWVmNjdlNTY5Y2MyMjg0ZmYwNzc3NmNjM2Y3YTNkN2VjNTBlODk1Y2EzYWM2ZTVmMTA1MjIzY2UxZTkifQ" + }, + { + "resource": "repo://CHANGELOG.md#L882-L884", + "version": "repo-lines-v1:sha256:00467d753a0da32db57853eea00c98ee7f3b7f7ab03ea5de72d72ec8335d49b9:eyJzZWxlY3RlZExpbmVDb3VudCI6MywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiOTZkYzc1NTI0YjU1NWExMjU5N2JlMDgzZTcyNDY0OTBiOGJlYzA3MmEzMmMxZmQ5MTc1MTFlMGFlMzE0ODViYiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiY2YxZTNmZDA5ZTE2NzkwYjNiZDc2MDdjYTU4ODc5M2U4ODA0NWUwOThkNTk5YmQ5ZjRmZTRlYmNmN2VhMzQ1NSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMGQyZWViMDM0MTBkN2Q4ZThmMWJkMGFlMWQzNmU1Y2MxNjY5NjBkMmE1ODVjM2U5NzQzZTc1ZGU1ZDM1MGQwZiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjAsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZTNiMGM0NDI5OGZjMWMxNDlhZmJmNGM4OTk2ZmI5MjQyN2FlNDFlNDY0OWI5MzRjYTQ5NTk5MWI3ODUyYjg1NSJ9" + } + ] + }, + { + "id": "claim_87fd9b52d6684af0b5d4ffbc55eedffa", + "statement": "All five 0.16.0 changelog items (session log browser, install-channel version badge, command snippets library, group-delete notice, and the SFTP ssh -G credential resolution fix for ssh_config-imported hosts) are documented as Covered: the log browser reads capped 4 MiB segments and stores bookmarks in the v15 log_bookmarks table keyed by on-disk location; snippets live in the v14 snippets table with rebindable Shift+S/Ctrl+N actions; sftp_ssh_host resolves ssh_config-sourced aliases live through ssh -G for both the TUI browser and headless `sshub sftp` while SSHub-managed fields win.", + "evidence": [ + { + "resource": "repo://src/app/util.rs#L283-L295", + "version": "repo-lines-v1:sha256:ab4c41a2185beb4ee3aedf452fcc8be6b02a4dbca1527444327d33b6fa66f7b7:eyJzZWxlY3RlZExpbmVDb3VudCI6MTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImY4NzVhNDQ0MzJmNjZlN2MxYTM3MTcwYzFjNWMwZDU4YzMyMTU3MmFmMzE2ODQ0MDU3OTEzZGY1NDExNDY0YWQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjExNGMzMDE2MjU2ZmNhZDM1M2ZmODM2MGQ1ZjQzZTEwZjEzMWMyZDRmYmExZDExNjViYzY3NTMwMzI3MGM0NmEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjMzZDZkM2Y3ZTBiNzI0MjVjOTJiNTFmOGQ3ZDcyYWM1ZmVkNTNkNzdjODFkZTQzMmI5MjkwNjQ0Y2ZkMzg0OWUifQ" + }, + { + "resource": "repo://src/keybinds.rs#L511-L513", + "version": "repo-lines-v1:sha256:87dbd4c198d711940d67b7d93fa99e6c5548c45acea10a8c872b255b4d035578:eyJzZWxlY3RlZExpbmVDb3VudCI6MywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZjFmMmUxZjYxZmM2MDcyN2RmOTk5M2I2Zjc4YzJkZjZhYmUwZTE0ZWE2MWIxZTIzYzc3YjE2YmExMWNkMDdlMCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYjc3Nzk2OWIwNDM5OGQ4OTZmOWQwMGU3ZWQzOTkwNTM4MDQ2OTIxNjQyNjY1OTc3YjdiMjMzOWIyZTVkODkzMCIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiOTdjYTFmM2NkNTI5ZmI0MGY5MzQ1ZmJlN2Q3OWEwOWUxOTg3MTM0YjdlZTI2ZWMzNzNkYTQxZjkwMGZlYTIwMyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiOGFjNDFlMDM0MmZjNDcxMDBlMzg5MTc1YWRlMjNmOTI2OGUwMDJlNTczNzRlYjkxMjlhMzVkNDg4MDFhOGUyOCJ9" + }, + { + "resource": "repo://src/log_browser.rs#L1-L15", + "version": "repo-lines-v1:sha256:788867ea80a80ca37b5700fa83f51a289146591c78fdf33527cd96f6747ade79:eyJzZWxlY3RlZExpbmVDb3VudCI6MTUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRkMGY0OTBkNzY0OTIwZWMwYjBjMDEwNjQ0YTdhNTBkMDMzNDM1YmJiOGEyM2M0NzI1MTc2MDQ3MjRlOTRkMTAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjEwZjAwYjJjZDI2ZGU4NTk4OTNkYWNkNWZmMjc0NGUwZjY1MjZhMTE2YWU2YjEyZTkyZDZlOWE0ZmRiNDAwMDkiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjgzYjdmN2NhNzFhOGEyOTAwNjBmNWUxNzQwOTU0OGE1N2MyNzJiNWM4Yjk3YmM0ZWViZDU2YTRjNzYyN2E5OWEifQ" + }, + { + "resource": "repo://src/store/migrate.rs#L500-L530", + "version": "repo-lines-v1:sha256:e793eebb121663fc62780307726ab63bcf3a5618520bd1db0537b95c088b9e41:eyJzZWxlY3RlZExpbmVDb3VudCI6MzEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNmYmRhYWJhZDQwYTgyOWJlZWNmMTE5Y2FmYWQwY2I4ZmRlY2E1NmYxY2I5MDEyZjZhOGZhODEwYjkwMzM3MzYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjM1ZjZmNDY2ZTU5Y2ZjMzA3MDNjNDVmNzJhZjY1ZGU1NGFjMzYzZDM1NGNjMWEzMzMwMzUwYWNkNDUyNDM0YzQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjY2ZTA4MTRjZTMxOTM3NjZiNTllMGNmZTZlMWI1ODgzMGJkMjhhM2JmMDk4MDgwMTlkY2I2ZGVmMDc3YmFhMDkifQ" + } + ] + }, + { + "id": "claim_637af23f16714d808f7aa426e9696a65", + "statement": "The 0.15.2, 0.15.1, and 0.15.0 changelog entries are all Covered: form-kill chords (Ctrl+U/Ctrl+W) live in src/text_input.rs with Option-returning active_field_mut preventing non-text-row edits; the alternate-screen wheel becomes three arrow keys (alternateScroll) with a once-per-session tmux toast and a selection that shifts only by actual view movement; terminal queries (DSR 6/5, DA1) are answered under a REPLY_BURST_BYTES/REPLY_BYTES_PER_SEC token bucket while PTY writes run on a dedicated thread behind a 64-entry bounded queue; `sshub exec` exits 124 on timeout into its own process group and audits `via exec` without the command; and the vendored OpenSSL feature is default-on with a dev-only --no-default-features opt-out.", + "evidence": [ + { + "resource": "repo://Cargo.toml#L38-L46", + "version": "repo-lines-v1:sha256:e226c47e919c89c2b1523f4130bcb22c44b745e9cae4e63d4bbf80ff164ef87b:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYjZmNmMxYzczZTQ2ODViNmY1NTc5MmFlNTM5MjU1M2ZmNzlkM2NjMDY3YjBiNzRlMDY4Njk4ZjZiMmM3NmI4MiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYzdlNzE4NDM4M2MzMDA1MDc5MDczMmY1ODkzMmVlMjdhYTRjMDNlMmU1OTgwZTEzMzYyM2I1NDM0YzY1YjAzZSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMDM5Njg4ODI0ZGRlNDdjZDQ4YjVjNjVkYTM5YzE4MTU4YmMyZDcyZDJjMDYzYzJjZWFmNjE5MjllZDQ0NjE5NyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNzI2MjZjMmFmYzJkZWVmZTc2ODRhMDg1NDc2MTgzMjRlZGQ2NWU5NDE2MzI2MDA5ZmFlOTU4ODRkYWQyZDRjYyJ9" + }, + { + "resource": "repo://src/app/types.rs#L1631-L1653", + "version": "repo-lines-v1:sha256:46a8212099952a577b537a0ff742daada572569fd8394a23efd88ce053ac9c93:eyJzZWxlY3RlZExpbmVDb3VudCI6MjMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImJhMDI3YTUyOTVlZTU1MzY1NGYyMDdhZDhiMDUyM2JlMGNlNDdlMzY2ODQyM2MzMzFmY2I2ZmJjN2JlN2NlODMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjM5ZTYyZjYxNmJjM2U2N2QzMDhmNWZjNzIyYWI3NTY5OTk3ZGUwMTViZTgzNzIxNWU3NTcyMzllMzU3ZDc2ODQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjgwNjU3NzM2MDEzYzkxOTJlZjg1ODQzNTliM2ZlOTE0MjBkZDVjZThkYzlhYjZhNWEwNzQ0YjlmMjg5MWE5YTYifQ" + }, + { + "resource": "repo://src/cli/exec.rs#L23-L25", + "version": "repo-lines-v1:sha256:fa24c90007c44230d8b987d45b17a7ca33d511025f17f0e93dbd8452a240a4cd:eyJzZWxlY3RlZExpbmVDb3VudCI6MywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYzc4NTFjYjE2YjA1MWE1MTEyMmNkYzNiYmU3MzY1ZmVmYjgxYmMzOTFiNjQyMmI5N2M4YzQ2ZjdhNzg4OGMzYiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMGM2Y2NlMjdmMDJlNTgyMWYzNzkzZjJjNDYyZTlmZWU4ZjUzNDAyMjI1YzNlM2Q4MDM1YjFmZWY4MmYxYjFlMSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMmQ1OTg0NTE0OGFiMTQzNzNlMDgyZTMxNWEwY2I3YTQ4MmUzM2E0YWJjZWVhYzhkMjIyODRiMWVlODNmODA5ZiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiYzc4NTdlOWVkN2Y5ODVkMDQ3MDg1ZDQ3N2NlZDAzZGJhNDQwZDExMzMwYzYxZjA5ZTc1OTI0Y2ExZTMxZWNjYSJ9" + }, + { + "resource": "repo://src/session/keys.rs#L114-L136", + "version": "repo-lines-v1:sha256:1a78dab0ec3c159186b9f1cd06a911e1f5e5d7f93a179eb7b5567c4d32c9f349:eyJzZWxlY3RlZExpbmVDb3VudCI6MjMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImIxNjE2ODE5YTg2OTRiMWNkZjFmNmU2Y2YxMDE2OWUxZmFiMDY4ZGZjNTUxMjdkZjZjYzRmN2Q4OGFjZmE3ZGEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImY2OWM0YzA1YmFkZDBlZmQ2YzQ1NDJiYjUzMGEwYjg5YjQyZDVlNTQ2ODQwZDYzYTgwZTdjYTZiMmU3Nzc2NzUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRiNDMzMWU4OGM2NDU3NzljOWM0MWQ3ZjlkY2UyNDhhYTM5MDA4MTdlMzY1YzEzNmI2OTcwMDkzMmI5MWJjMzAifQ" + }, + { + "resource": "repo://src/session/mod.rs#L1021-L1028", + "version": "repo-lines-v1:sha256:f83a9903a78e1ac03d69f57df9394fb7c4ba5bd96d6646ce1c1d0575efc50b7a:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYmUxZDgxMTQ2NjdhYTdkYTAwYmIzMmQwMDYzOTY2ODU3MTllZDA0MTE5MGIxZDY5NjUzMmEyMzVjOWEwZjMwZiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMGNhMGMzYWMwMjMwYjc5ZjFmYTk4YTk3MDBlMTdjMGIzZGM0NWI2ZTExZTk3MWUwNDU2ZDc4OGNmZTU3OTgxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYzAzMTYxOWZmODcyOGI5ZDA5MWUyYjlhZWQwZTk5MDgzYzI4MTliYzNjY2MxYTFkNGRjNzE1MmI5MDllNWYyYSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiYmQ3NWU0MTM4N2E5YzIxNWJhMmQ3MzY4MGE1OGI3MTQ0MzczODgwNGJmNzRkZjFhZGM0YzlmNWFmZGVhNzZkNyJ9" + }, + { + "resource": "repo://src/session/pty.rs#L21-L32", + "version": "repo-lines-v1:sha256:346ad20d0ff54f14b245c7fabcd0225396d2977b7c9fb2ba9d70b207d4497420:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM1MmVkMWQyN2QxNTdkYWNjZjExYTQ1YmRkMjkyNzk0YjBlNmMyYmJmNGFlOTk2ZDU0Y2I2ZmFkNGZjOWM2M2EiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU3MWQyZThiZWE5ZTcwODk0Y2I5ZmY3ZjFlMGMzN2M2Njg2OThlNWNhYWI0ODBmOTQzODcwYTI1MjgwMWVmNzkiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE1Y2VkMjE4OTFlZTZjMjcyODlkOGQzZmRlYWJiZGViYTU0YzFmZjkzZWI4OTVhY2I0YmNiY2Y5OTlhMjBkYzAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijg4ZTNhMTdlZGM2MjFiZmFlM2RjMDllYTliZTFiYmMzYzgyNjU3NWViN2ExNWZlZGViNDBjZjk0YzMwNmQ1MGQifQ" + }, + { + "resource": "repo://src/text_input.rs#L52-L80", + "version": "repo-lines-v1:sha256:0183a92cbea2d02733c1e2ce864a40b711abc407b94075473979851fed89236f:eyJzZWxlY3RlZExpbmVDb3VudCI6MjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ4Y2ViMGYyZDNiNDcwYjY0ZjA3MDk0MmZmMzlkYzZhMjZlNGNkNTBlZTVkMDZjOWRmZDI3MzMwZjdlMmUxMDkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImViNTMzOTA4ZGU3ZDEyODU0MTllN2Q3NWMzODNlZWZjMDNhZGUyZTQ2ZTVmOTBjOGE4M2ZkMGYwYzZkODY1MjMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRlNzlmYzhhY2UwNjE0MzA5MTM3MmYwOGQzNzhlMGVmN2I2YmYxM2RjMzAwNTQ3NzNiNzIxMjgzYmQ5NzMyZDQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE1YzIwZTYwNTE5MTU3YTBjZjNiZTFhZTNiM2NiZmQ4NzY3NDc5ZTY2NDc4MzlkZGVkNTIwMTZiMzZmYzEwZjIifQ" + } + ] + }, + { + "id": "claim_dba47efa8047403d837fce0770e5653b", + "statement": "The 0.14.0 and 0.14.2 changelog entries are all Covered: the TOML theme system (three-layer format, 234-role catalogue, five built-ins, PTY ground written as a pty_background/pty_foreground pair, headless `sshub theme` CLI dispatched before any database opens), profile isolation, the two independent transparency toggles that replaced the retired opaque_background key, per-line known_hosts fingerprinting with symlink and negated-pattern deletion guards, OpenSSH-style quoted-alias unquoting pinned by a real-ssh differential test, theme persistence through App::config_target() (the profile-aware writer), and the leading-dash option-injection guard (safe_ssh_target rewriting to ssh:// form plus reject_option_like refusing leading-dash name/address/username at write time).", + "evidence": [ + { + "resource": "repo://src/app/keys.rs#L1401-L1410", + "version": "repo-lines-v1:sha256:7895a6b319098beadf3e95fbcd07ff7b9231ce25d90f1b5783b2e5295f7e90a3:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImEzNGFiZTU2YzE2MzA3ZjUwZGM4N2VjYTAzNTE2NjkyNjdiNzY5MDRjMGI3MTFlZmE2MGFlNDYzZmE3ZjUxMzAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE2MmVlNTdlNDFhOGMyMGFlMDE2NGRmMjYxNDM5ZGNjNDk5ZDM1MjA4MzQxNTE1MTZkMDQ3ZThkZWFlOGE0NjYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRkMWZiYzUzZDk0NDgzZjdhODcxNTgzMTNjOTViZmYzMmM4NDAxZWJjOGJhZTA0M2ViZjg0NDQ4Y2I1YzI5ZjMifQ" + }, + { + "resource": "repo://src/config.rs#L417-L434", + "version": "repo-lines-v1:sha256:4bc77bf5873313336d539a469d122e6d94f98034b294f2787be9c8df08e1ad18:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjJlNTg0NGRmNTg3YzhkNTBkYmJhMWQ5NjdkNDVhZjEzODFiYmY2N2ZlMGJiYjFmMTkxOTU3YTg1YjI0MjQ5M2IiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA1YTliNjQzODNmZWE4NTg1ZmE4MmM1Nzg0YzI2NzM0NDljNjgyOTE2NDRjZWIyMGU3YmI3NTcxMmE4NDk0OGIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk4Y2NmMDk3NjI2M2M5NGQ4NTZhZWE3MDBlOGJkZTRlMmI2ZmRkOGViNDQ3OTI5OGYwMzcwZjA3ZDU2OGRkMmQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImY1ZWJmYzBkZDc3MDMyYjBjNWFhMzZkZmExMTdiMjJmMzQyNWE3NThiODQ4NjgzZTkwM2EzMWI2ZjdmNDU3ZDEifQ" + }, + { + "resource": "repo://src/known_hosts.rs#L118-L174", + "version": "repo-lines-v1:sha256:f35df16e80a12385a1ce9d58abaeb6afea392c2cc29fd24b3bf5cc9e77a3542d:eyJzZWxlY3RlZExpbmVDb3VudCI6NTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRkYjliYzZhZjhmMThmM2Q0MGYxMDEwNTBkYzgyYmM4MGY3MDAxZDA5Yzg5ODNlOWUwYTNkMWU5NDgzY2IyN2EiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijc2ZGNmMjI5YjZmZTExNWYyZGRmYWY1ODg2NzJlN2ZiNjMyNGUyMzRlNWFmYTA3MDcyZWU4OWM4ZGE1ZTkxNjQifQ" + }, + { + "resource": "repo://src/main.rs#L37-L43", + "version": "repo-lines-v1:sha256:928fd295a26a9e0a2d143b2e0b7c628827081e48277d1c7e2fb6ca15322c9b04:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYTlhYTNjYTQ0N2E5Y2VmOTMwN2QxNjg0NDIwZWYzZTEyZTZhNmFlMDg0N2M1OWE0Y2VlNTNiZThjZGI5ZTcyYyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMzY4MGQ4MjI2YWZmZjZlMWQ2OTQzNTg4YzVkOWRiMmVlMjFkOWQ3ODZjMjNlYjIxYTYzNjIxZDI2ZjRkYzBhZSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMDVlM2NhMzQxNTE2MTM1YjNiMGJkMTgyMmE4MDhiOWNkMmUwOGQ1MTE5Zjk1ZWZkOTllY2M3NTNiNDgzYjBlMiJ9" + }, + { + "resource": "repo://src/ssh/host.rs#L88-L107", + "version": "repo-lines-v1:sha256:0855157bae5856cb010151d37ec33abc461233c80742b5d9cd9c0057cf0028b9:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRhZDMwNDVlODI0NDA2MmYyMzYwYTU3MzdmMTZmNDkxYmNmNDJlMjJkOTg5MWIyYTk1YmRhNTJlNTUxZWVkMGMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk3M2ZhMTMzYzY3NTU0MzE2NzIyZGI3YWNmNDI2NDAzNGNmZGRhMGQ5ZWNkZThhNWM0MjY3NjVkOTQxODA5YzciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImM1ODc1ZDMzNmI5NmVlMGNkZjdlNmJmYjg4YmU0NDRhYWQzY2MwMGZkZjg1Yjg3MmZiYjRkNTA2NTAyZTMyZWEifQ" + }, + { + "resource": "repo://src/ssh/resolver.rs#L221-L236", + "version": "repo-lines-v1:sha256:6837447e3a10d1901462f880c8e7c568f4af0eaf1057792ef0911ce08ff4b4bb:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkxMWU4YjYyZTMzYmM0ODQxYzg2YTUyYmJmZDQyNGFlNDFlMGJiZWE4MjQyMmQxMGUwZDIwYjlmMzA1ZmI0MGMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE5NDBkMGMxM2EyOGRhZDkxOTBkMjA2ZmQ2MGRiOWViYzUyYzJlZGNhODBiNjg0YTFkN2JhZTgxNzVlMzFhNWQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUyOTk4YzJhMmE4ODFiNmQwZjYzMTY1NjFhN2RjMzRhY2NmYTAyNTdkN2EzYmM3MWQ3M2Q2NWZiM2E2Y2E5N2UifQ" + }, + { + "resource": "repo://src/store/hosts.rs#L887-L910", + "version": "repo-lines-v1:sha256:2c9942e77b2b2c22ec28529cd519206744a9585b8236d598359faf88787a4eed:eyJzZWxlY3RlZExpbmVDb3VudCI6MjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjYwOTNiMTNkOWQ0ODNhZjFiNGRlNDIwN2QyYjYzMjg0Y2ZjODE3MDRlMjk4Njg2MWM3MWRkOWFmNDg1YWY0YWQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImEwYTEyNjk5YzE4MDk4MDg0Mjk3OTNhOTNkZGU2MDcwYTU4Y2VlMzBmZmIxMmQ4MTEyY2FjNjI0MmU0YTI3NjEifQ" + }, + { + "resource": "repo://src/theme/catalog.rs#L258-L265", + "version": "repo-lines-v1:sha256:143bd168c3b3284c46a3912a739fde014ddbf672b94a07c0ad9e388fd920ef04:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNzg3M2M3ZTI0ZmNkYjY2ZDI0YjJhMjA5YzkxODRjNTRmZTVjNzVlMzJjNTYwNTAyY2IwNjQyZGJiZjNhNjE4YyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMWYwY2RiOTcyYTdiZmI3ZWQyNDZmOWZmNjcyMzg1ZTU1ZjQ5NjhmYzExMDQzZGM1YjY4NGQ1MWRmYTMxZGJjYiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiY2VjNzQ3MDg4MjIzYjkxOTAwODg3YjVlNThlMzJkNzdmMTgwZGMzN2NmYTU4MmQyMTY5MjJiMmQ3MzFiNDQyMyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNjI0ZjdmOTkyYWIwMDYwZjBiNDdhNzZlZDBiMjk0NDc2ZGViZmVlZjY1NzEzMjQwNDVlMTcxNjg2MTBmZjA3NCJ9" + }, + { + "resource": "repo://src/theme/role_matrix.snapshot#L1-L10", + "version": "repo-lines-v1:sha256:7f6689d802944d5ba64edf9285c5b87a930a6c44daf2fac8b929ecdef6c7198e:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk0MzRiZjljNWQyY2IwM2NiNjhmMWI1ZTliZjJkMmZlZjllODE2YTUwYTY0N2M4Y2JlYWFhOWQyN2NmYjBlZjkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjJiODM5ZDkzODIzOWU3YWQxZTI1NzcyMmNmODg4MjUyMWY3ODY2YTdlYzliYWMxNjEyYmEyM2Y1MTZjNGU5ZjkiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjY1OWI1MmJlYWQ0M2YwMWE3M2U0M2U3YmJjODFhYjk2NGFkNDgxNWJlMWQ4YmVhN2JiOGU2ZjJhZTc0MjIwZGYifQ" + } + ] + }, + { + "id": "claim_0831530cddd84df9bcc7282d79848088", + "statement": "All previously Pending ledger items are now closed as Covered because dedicated pages exist this run: 0.11.0 public-key push/key generation → domain/hosts-identities.md; npm installation → integrations/external-terminals.md + operations/build-release.md + operations/ci-cd.md; 0.11.0 UI motion and regression fixes → workflows/tui.md and workflows/session-terminal.md; 0.10.0 panel focus/zoom → workflows/tui.md, PuTTY/mRemoteNG import → domain/hosts-identities.md, broadcast → workflows/broadcast.md; and 0.4.0 AGPL relicensing → the quickstart license note.", + "evidence": [ + { + "resource": "repo://Cargo.toml#L6-L6", + "version": "repo-lines-v1:sha256:12ebd0d5ab9e7dea64dbeb5c04b09cf47fa32aea0f10b092e46373eeced305df:eyJzZWxlY3RlZExpbmVDb3VudCI6MSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMTJlYmQwZDVhYjllN2RlYTY0ZGJlYjVjMDRiMDljZjQ3ZmEzMmFlYTBmMTBiMDkyZTQ2MzczZWVjZWQzMDVkZiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMTJlYmQwZDVhYjllN2RlYTY0ZGJlYjVjMDRiMDljZjQ3ZmEzMmFlYTBmMTBiMDkyZTQ2MzczZWVjZWQzMDVkZiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNzQ5YjU3ODkyNGJiZWM0NTlmYzhjNGU1Y2IwYzJmMDcxODNkNjczYjcwMGI1ZDMzZWM1YWJmY2FiNmI2MDY2NiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiODVlMzA0MjYzNmNjNzY3MWUwZTdmZTk3MDVmZThmOGFhZWNjNDAwYjFiMjhhNjIyYjEyN2QwNTBhZWE0YjY1YSJ9" + }, + { + "resource": "repo://npm/wrapper/bin/sshub.js#L14-L65", + "version": "repo-lines-v1:sha256:9327e17670882283778a772ff5ea65803419f898e49e95bb1424359aba30376e:eyJzZWxlY3RlZExpbmVDb3VudCI6NTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjhjNDViZmI1NzMwOTY1NTA3MjkwNTJlY2I2ZWIyZDJkYTBlM2UwMWQ3MzBhNzFiNDBjMmJlMWNkMzYyZjA5M2QiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNhM2U0NmY5NDI3NmI2ZjZmNGVkMWYwMmFmNzhiZWY2YTJlZTFjNDM5ZGQ1NTg0Yjk3Mzk0ODMxMzIxNGQyYjciLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjVkNGE1ZTg4M2RlOGNmYWZhYzE3MmFjZDQwMDRiZmI4ZTEzNWQ5MWYzODAwMjY5YTJkZDMwN2VjOWY0ODkyNDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjkyZTk5NDY4MDIwMWU0NjQxYzhjNDllZDMxMzU2NmVlY2YzYmIzMjY3MGJkYTMzM2UwYmIwZjAzZmI4MzYyNTEifQ" + }, + { + "resource": "repo://src/app/push_key.rs#L10-L33", + "version": "repo-lines-v1:sha256:bca449d1ca7a57cd025c31426e7a8acf9aaa1de488065715523851d09c98b3b7:eyJzZWxlY3RlZExpbmVDb3VudCI6MjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk1Y2ExNTAxZTU4M2YyMzgzYzY2NTMxYTdjYTNiOTVlYzhmNGFkZTU3YmIzOWE1MTYwNWViMTczYjBmYTJhZTciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImNlMzAzMDEyMTY4Y2VlZGQ4YTFkMWE2ZWNhMDE3ZTE2YjU1MzZiOWFlZmU0ZTU5ZGNkOGJkYjk5MjhkN2I5ZTgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQ5OWY4YzdjZDU0NzJhMDU2ZTBlMDlhZjYwZDU0YmJlYTE4OWEzM2Y3MzU5NDBmYTNiMjdjMDE3NjhiZTEwZWMifQ" + }, + { + "resource": "repo://src/config.rs#L41-L50", + "version": "repo-lines-v1:sha256:424497c46bd7deb142cdf8a055981ab7cea66000ac17c941b9a5c9b175b06104:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjdiYWJjMmEzZWJlMTI4ZGM2ZDY3N2E5NzgyYmFiMTE0NjJkMGIyZDZkZGRiMGM1NzMzMzljZGI1ZTE4MjgzYzAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijg4ZjdlOTQwZTQ3OWEzNTdhNjI0OGUyYjhlOWQ1YjZlZjBlMmQwZTkwOWRmYTYxNWFhNmU5YzdmNDEzMzg5ZTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijg0OTliNGFmNGUwOWZjNmU2NTJiOTc1ZTA3NjQwNmM4YTdlMjJkNjc1YTE5ZmNmNzI1YTg5YWU3OWZiMTY2ZjIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE2MzE3MmZhMWU0NDMyNjA1NWYzYWQyZjAxZDNlMjc3MmE0YmMyOWNlYTk3NmI4MGU4MGQyZDRkYjkwNDI0NDcifQ" + }, + { + "resource": "repo://src/keybinds.rs#L506-L513", + "version": "repo-lines-v1:sha256:94eb1b709f4ea7ecdc9287f82c53fde9dfa494c828a02832d560f39011ee1145:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZmQ4ZDA0ZWEzZmRhODNiZTE5MTBjY2UzOTNhYzEwODZmYjFiZTgwODRlYzMyYTkzNDljMzJlYzcxNjYxY2QxNyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYjc3Nzk2OWIwNDM5OGQ4OTZmOWQwMGU3ZWQzOTkwNTM4MDQ2OTIxNjQyNjY1OTc3YjdiMjMzOWIyZTVkODkzMCIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZDI2MDU0ODI5YjMwNTU1NDJmZmZmNjkyZTU4ZGE2NmM5MDRkNDZiYmYyNTUyOGRiNzhiODc4M2YzMDczMTQxMyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiOGFjNDFlMDM0MmZjNDcxMDBlMzg5MTc1YWRlMjNmOTI2OGUwMDJlNTczNzRlYjkxMjlhMzVkNDg4MDFhOGUyOCJ9" + } + ] + }, + { + "id": "claim_83905dc610c349ba8caddb4ac39cd0d8", + "statement": "Three remainders are honestly unresolved because the source itself carries them, not the wiki: the host-sync P2P feature exists only as a reviewed design doc (docs/host-sync-design.md, epic #13) with no implementation in src/; detached tunnel spawning has known PID-file races (no advisory lock between the stale-PID/port checks and the PID write, and liveness via bare kill(pid,0) with no start-time identity check) documented as acceptable v1 limitations; and AskpassSecret removes its staged 0600 secret file only on Drop, so a SIGKILL leaves the file behind with no atexit cleanup.", + "evidence": [ + { + "resource": "repo://docs/host-sync-design.md#L1-L8", + "version": "repo-lines-v1:sha256:eb6412fefd58d4e4f0df3c9168288eb0b402b664558ff9a86fe38c62b96facf6:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMWI3ODk3MWFlMjg4ZGRhZTg1YTQzYjk4YjIwM2MxM2FlOTM1ZjRhMDZlNDRhNmUyY2ExNGYwNGM1OGUxNDNhZiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMDdkZjA2ODU3MzMzNWNkZjZlMThlMjE1ZDg3OGRiZTUxYjI5ZmY5NWM5OTVjOWMxZWJjN2I1NjcxYzI0ZDRiZSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjAsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZTNiMGM0NDI5OGZjMWMxNDlhZmJmNGM4OTk2ZmI5MjQyN2FlNDFlNDY0OWI5MzRjYTQ5NTk5MWI3ODUyYjg1NSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNzQ0MTZiOThiYmYzYWRiMGM1ZWM5Yzc2ZjMzZWIzOWRmYTM1ZTYxNDE0MThmNGQ4NTY2M2E3YTljZmIxNjU2MyJ9" + }, + { + "resource": "repo://src/session/askpass.rs#L56-L100", + "version": "repo-lines-v1:sha256:7ce843fe06b65f671622ab3d0627d93fa225c7e8776817bd66a5035d2bb0be2e:eyJzZWxlY3RlZExpbmVDb3VudCI6NDUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVjN2Y0ZTU1Y2E4MWVhNTliY2NjNmFlMjM4ZDUxYWNhZWQ2YjFlNmU1MjUwMTM4NTlhZjc1OGEzMmZkZmFmMDkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjZiYTk1NWJlZGVmOGRjMDZkYWRlNGYzYzRiYjg2MzcyODY5MDBmMTExOGM4ZDgyNTk0YzIzN2QzNTYzMmI2ZGEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImExZGJhZGE5OTczYjUyMGE0YjMzMDY3MTIyMTc0NTA0MmQ5Y2I3NjI5ZmVlN2VjYTZjZmEwNGVhMzYzMjQ4ZjcifQ" + }, + { + "resource": "repo://src/tunnel/spawn.rs#L243-L250", + "version": "repo-lines-v1:sha256:1ceddd3bfbe8f9554f261bcb48f4e001b190d5b7187637da0df57807e9f8a83d:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZjZmNjM2OTNjYWRiNzY3MWRhMzgxYjBjNzYyZmQwNTUyOGMxYTdjZjJlYWJmM2RlOWM3NGU3ZWE5ZGYzNWU4MiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMzU0ZDMxOTMyMGFjNDFjNjQwNmVhOTY5OWM3MTVjMmYwNDlhY2U2OTJkZDM5Yzc1NzY4ODhmYjkyZThlZjExZSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMTllNzYzNTVjZjExOTk0ODBjM2Y5OTgzNmIxNDg2Yzg0MWQ4OTFmYWFkOTA0YzYwOGUwOTMwMjJkMzc1N2FlZSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMjNhMDNmODFkMDZkZmVjNTdiMDcxM2YwNGNiMDQ2Y2MwYTVmYTRmZmZhYzVkMTI3NGRlMjc0NjdlNzI1MmNjZiJ9" + } + ] + }, + { + "id": "claim_5cfbda6673c54cc994224a195c09a9f6", + "statement": "The Last-audit block in the ledger records this run's metadata — gitHead 7c69f7e4242acc8ee9a717ad7e7f7edfd796ee99 (the run's targetGitHead), auditedAt 2026-09-11 (the run's startedAt date), model z-ai/glm-5.3-flash (the run's metadataModel) — and carries the three-item unresolved list, replacing the previous run's 6e10d889…/2026-08-03/gpt-5.6-luna metadata that stopped the audit at 0.13.0.", + "evidence": [ + { + "resource": "repo://CHANGELOG.md#L7-L7", + "version": "repo-lines-v1:sha256:5b7f94aa9c8c5b4840bf0473fb91e17c079f7f127d64197e6b40641702040bb2:eyJzZWxlY3RlZExpbmVDb3VudCI6MSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNWI3Zjk0YWE5YzhjNWI0ODQwYmYwNDczZmI5MWUxN2MwNzlmN2YxMjdkNjQxOTdlNmI0MDY0MTcwMjA0MGJiMiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNWI3Zjk0YWE5YzhjNWI0ODQwYmYwNDczZmI5MWUxN2MwNzlmN2YxMjdkNjQxOTdlNmI0MDY0MTcwMjA0MGJiMiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZTM2OWRkYjgyMTcwMWZkOTVjNzM2OTdhYTQzYzA3NTc3MzAwOWMzNzExZmM0NmE4ODQ1YzI4ZmIzYmY1N2JhNiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNjA4NzdhN2MzODZhOGUwNDhhYTI5MmUxZGE0ODc3OTAxN2U4ZmQyNWI3ZjI3NGI5OTE5ODM1ZmRhOWM2NjA3ZSJ9" + }, + { + "resource": "repo://Cargo.toml#L3-L3", + "version": "repo-lines-v1:sha256:be10d6b705ed8160ef2f599f984ea4307f7b9d655af26091c5209446d99f79d0:eyJzZWxlY3RlZExpbmVDb3VudCI6MSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYmUxMGQ2YjcwNWVkODE2MGVmMmY1OTlmOTg0ZWE0MzA3ZjdiOWQ2NTVhZjI2MDkxYzUyMDk0NDZkOTlmNzlkMCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYmUxMGQ2YjcwNWVkODE2MGVmMmY1OTlmOTg0ZWE0MzA3ZjdiOWQ2NTVhZjI2MDkxYzUyMDk0NDZkOTlmNzlkMCIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjIsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMjQyYzkxZmFlNzEyMWE2OTEwYmYxYTFmMTVkY2FkNmM4ODdhYzBmYjFiODdmZTdiM2NhYjA4NzNiMzI2ZjFlMCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiODJhZWE3NjU1NGM3NzhkM2VkNjM5OTEzOTRiYzY0NmVlNGEwZTc1MzMyZGVlNGNiODU3NDAwZDVmNWNlZjRkNyJ9" + } + ] + } + ], + "verification": { + "by": "openwiki/0.5.1", + "at": "2026-09-11T12:46:43.817Z" + } +} diff --git a/openwiki/.claims/domain/hosts-identities.json b/openwiki/.claims/domain/hosts-identities.json new file mode 100644 index 00000000..080e0994 --- /dev/null +++ b/openwiki/.claims/domain/hosts-identities.json @@ -0,0 +1,442 @@ +{ + "schemaVersion": 1, + "pageVersion": "sha256:ae1b8961cc4017bec463dd5a8357882ed52f67148306abbd993a6210415ab0f3", + "claims": [ + { + "id": "claim_33afbc69276a44ef9da804794f4cc137", + "statement": "The user-facing host list is a three-way merge produced by `load_merged_hosts`: it first syncs ssh_config rows into the DB, then loads launcher and ssh_config DB rows, then appends legacy `HostEntry::Legacy` aliases for ssh_config names that have no DB row, with metadata defaults applied.", + "evidence": [ + { + "resource": "repo://src/hosts/loader.rs#L10-L53", + "version": "repo-lines-v1:sha256:03415d8e2768c4a60ca1613820bffbcbe3fd982da1f8bb9097dee5ab9986f417:eyJzZWxlY3RlZExpbmVDb3VudCI6NDQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRmNDQ0MGJiNjdlNjllNTQ2YzJhYTU4NzYyZjdmMDhkMWVhMWM2NGNlMjcxOWY5ODhkNWRkZjBiZTk5OTAxNTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImY5ODM1MmRkOWQ3NzY3Y2FjNWQyMmRkODM3OTBmYzdkMGVmYjU1ZjYyYWU3YzNmZTYyOTIyYzI0MjY1YWU5OWQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjAwMzk1MTkxNDk3NjY4YzVkNGRhZDA5MGIzZjY2ZTJiYjVkNDRjY2M4YWRkZWIxYzUwN2UzYTk3MmVmNWVhOWQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEifQ" + } + ] + }, + { + "id": "claim_6d7cc6da331a423aa40a3b6da5d7a67a", + "statement": "Import/sync can never overwrite a launcher-owned host: `upsert_ssh_config_host` returns `UpsertSshConfigOutcome::SkippedLauncher` when the name matches a `source=launcher` row, and updates to `source=ssh_config` rows preserve user metadata (tags, notes, favorite); the background sync re-upserts a row only when `compute_ssh_config_hash` reports drift.", + "evidence": [ + { + "resource": "repo://src/ssh/import.rs#L24-L50", + "version": "repo-lines-v1:sha256:95b31c735c35192941f4cb1d5706204b053386c6b76649f58ac6b66887854b6e:eyJzZWxlY3RlZExpbmVDb3VudCI6MjcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ5ZGQzNWViMTI0NzQ1ODdkNDc3ZjliZjA2ODRkZTYxZTU3ZGI2NDBlMWUwYTE5MjY4NWU2NjA5ZTBkMDFkMTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQ1OWNkYmFlYTM3NjJhNTFkZTQ5MTZmMTBjNjM5NjMwOTlkNmNiNGU4MmI3ZjM5ZmE2Mzk5NDdiYWIwMjhmYTciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjY4MGYyMzQ1NTBhOTE5YWVhZmYzYmVmYTkwNWVjYTMzOTQ0ZDk2ZWE0NGRkNjdjYzQwMzhhZGE1ZWE2NmY5MzQifQ" + }, + { + "resource": "repo://src/store/hosts.rs#L518-L556", + "version": "repo-lines-v1:sha256:8652f2efb3df4256d83f8f9957622492ce99fb6f51e41f4c18e492bbe470a18f:eyJzZWxlY3RlZExpbmVDb3VudCI6MzksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBlZDFjNWQyNzA5ODk5ZjdhZTU5NzUyYjE1NGVlNWU3MGNiZmExNGM4OWI3MmVkOWFiNjNlOTIxYmQwODhhNzEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImEzNmZlNjc1MzY5YmZjZTYwMWE1MTVhOGJkYTQzYTcyNTQzZjg4OGY3NjUzOGM2NGJlMTU3YzZmNjYwN2ZjNjAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImNkZjkyYjJjODA2YjU5YjczMjAxOGUyYWVkZmY3Y2QxYmQ0MzI4MzJjNjM0MGE1ZGVjNzgzMzcxZGY0ZjVlNGEifQ" + } + ] + }, + { + "id": "claim_b35dff98ff4f40d59b2cdbd55f2d74f4", + "statement": "Host resolution goes through the `HostResolver` trait / `SshConfigResolver`: alias listing parses `Host` lines while following `Include` directives (recursion depth-capped at 16, with `*`/`?` glob support in the final path component), and effective options come from the real `ssh -G` (hostname, user, port, proxyjump, identityfile, forwardagent, remotecommand). The config path is `~/.ssh/config` unless `SSHUB_SSH_CONFIG` (fallback `SSH_LAUNCHER_SSH_CONFIG`) overrides it.", + "evidence": [ + { + "resource": "repo://src/ssh/resolver.rs#L126-L155", + "version": "repo-lines-v1:sha256:a526425050d5f780537719461c70a7b21576f5785649fdf4e97095ca41325d52:eyJzZWxlY3RlZExpbmVDb3VudCI6MzAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjczNjhlZmEzMzgyNjQ1OTIzMWY4MWJhOTg4ZTFmYjViN2UwNmIzNzRmNTQ5NDI2N2Y3MzcxMTk3YmFjZmY5NDIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjMyOGVkZjg1YzFlZDIwNDRmODY1MmMwOGE5NDA0NTRiZDI2NzlhMTYyNDYyZmJhZDk3YjNhY2ExYjE1ZmRlNWYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjczNGJhOTYzMGIzNDhkOGMyMGUzOWNiZWY5ZWYwYjY5YzM4YmQ1YTY0MzMxZDkyMzE2YjAxNGY4OWMyYzk1ZDkifQ" + }, + { + "resource": "repo://src/ssh/resolver.rs#L239-L271", + "version": "repo-lines-v1:sha256:f31a8e8958e74a08a84f86b82a5c03a4f5293596bdac73205daad0d786a75d5d:eyJzZWxlY3RlZExpbmVDb3VudCI6MzMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA0ZGViYzllMWM4MmVlOTA2NmNmYTI2YjNkY2FlYzA3ZjI3MDg4ZjdlN2U0ODQxYTE1YWM2Zjc1NTk3MzJlMzciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjEwYTMwNDE0ZjY5MGEyZWZjMTA5OThiOTQ4MWQzMTg5N2JjMDE2YzZkNTE0MjM4ODQzYzhlZGE1MTUxZTA0MWYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjdmOTc4NDQzNzQ0NWE0NmVhNDNhM2RjNGQ5MDU1N2YxY2JkYTc4YTEwZmFkMjUxOTExMmQ0MmYwOTRjZTU0NjEifQ" + }, + { + "resource": "repo://src/ssh/resolver.rs#L87-L96", + "version": "repo-lines-v1:sha256:3cb4ab5ac761f4e4b2e1ee81ac496431adbe0451567a84146818e5f90d42a06a:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjY3M2QzMTkzZmY0OTk3MWZmMGI1ZGJjY2MzYjE3NzUxNzNiM2MwNTdkZTU2OGMwY2M3NjE0NDJhNWNmZWY4ZTAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImJhZGI3ZTcxNTUwMDlhNmQxMjZjYTZkNDU5NjgyNDM0MjQ1ZTk2M2ViZmYyOGFhMDU3OWMzMzIyOWNiZTA2YzUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImNlYWI0MDc2ZDRmODFmMmU5MDRlMTAyNGU4MTIzZjBkMzQ4ZmNkNWFlZTNhYzQ0Zjc5ODE5ZmE3OGRkNTY5ZjkifQ" + }, + { + "resource": "repo://src/ssh/resolver.rs#L9-L13", + "version": "repo-lines-v1:sha256:60459dac19dd9edc4d904fbe9985f37f332e0713c29377ef69abcde27f623085:eyJzZWxlY3RlZExpbmVDb3VudCI6NSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMzhhYzQ0NzlhNjEwOTc5ZmFlOGVmM2Q3NzMzNDNkOTgyYzY0M2RhNDAzNDg4MWYzNDczZGU0Y2Y5MjAyNGFjMyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiN2M0NDMyZjZlMTU1ZGYyMWQ3MDFkOGI3YzVlYzAwMGVkZDAyOTllNzU1YzM5ZGI1N2QxZGFjNGMwMmUzNjU2MCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNWFjYzliMDVhOTBhZmZmNjVmNWZkNGQwNDZkNzczMmU5YjNkMjQ4MWRiMzBiNjQ4NzA3MTM0MWRmMGQ4ZDZjOSJ9" + } + ] + }, + { + "id": "claim_9dc0b7a0e19349d8b700e67bfbf1840d", + "statement": "Since 0.14.0, quoted `Host` aliases are unquoted the way OpenSSH unquotes them (`listable_host_alias` strips surrounding double quotes), fixing the earlier bug where a quoted alias listed verbatim and `ssh -G` rejected it with `hostname contains invalid characters`. The hand-rolled `Host`-line parser is pinned by the differential test `listed_aliases_round_trip_through_real_ssh`, which resolves every listed alias through the real ssh binary and degrades (skips) rather than fails when ssh is absent.", + "evidence": [ + { + "resource": "repo://docs/oracle-tests.md#L30-L58", + "version": "repo-lines-v1:sha256:24bcef638a0f999644c403e5a663380c710856fb0559bf3ad1429dde655ef2e0:eyJzZWxlY3RlZExpbmVDb3VudCI6MjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFmMzhiOWYzY2M3YjE1YzY2YWMwNjEyMjQ4YjgxYTI2ODc0NDcwNTRhNDJiMTYzNWE1YmViYzdmZjkyYzA4NjciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjVmMjI5MTUxMzQyNTkxZDkyMTBlZDZiYjE0MTEwZmZkMzU4NmFjYTQyNzZkN2IxYzg2MzVlODJjMzJmZWZkZWQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk0MTc3ZDI3NzU0NzNhMWRiZjQ2NTg1YzYwMjQxNGE4NmFiMmYwYWYxMGUyZjRiNzRlMTRmZGQ3N2RhOGE0ZmYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImM1ZGRkMTk3ZGIwNWU0MDI0YmY3MDk0NmE1NzVkYTdjODg1ZWUyNDllZmM0NTEzZDZjOTRhZmM2MzM1NTVkNzUifQ" + }, + { + "resource": "repo://src/ssh/resolver.rs#L221-L236", + "version": "repo-lines-v1:sha256:6837447e3a10d1901462f880c8e7c568f4af0eaf1057792ef0911ce08ff4b4bb:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkxMWU4YjYyZTMzYmM0ODQxYzg2YTUyYmJmZDQyNGFlNDFlMGJiZWE4MjQyMmQxMGUwZDIwYjlmMzA1ZmI0MGMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE5NDBkMGMxM2EyOGRhZDkxOTBkMjA2ZmQ2MGRiOWViYzUyYzJlZGNhODBiNjg0YTFkN2JhZTgxNzVlMzFhNWQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUyOTk4YzJhMmE4ODFiNmQwZjYzMTY1NjFhN2RjMzRhY2NmYTAyNTdkN2EzYmM3MWQ3M2Q2NWZiM2E2Y2E5N2UifQ" + }, + { + "resource": "repo://src/ssh/resolver.rs#L441-L500", + "version": "repo-lines-v1:sha256:709f9af76d173d1c43d54206834153a73b3762e20d319217750a044146424541:eyJzZWxlY3RlZExpbmVDb3VudCI6NjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjJiMmQzMmVlZDIxOGZiZDk5YTgwOTEwNTk5ZmFkZTdkMzg0M2M2YzJlZjIwMTU5Y2M3OTE4MTVjZTVhYTI1YTYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImIzMGUxYjc0MTVjMzRhZmJkNDViM2Y5Mzc2ZWM0YmRhOTdhYmYyZTQ1MWI5MDYwZDNkZjk0ZWZmMzE4YTY5NjgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImFhNDAzYjBlNWJmNzcyNGVkYWE5OTQ1NTA1MGEyODI0NjM5NGI4ZTBiZDk5MmI2ZTg0NWYxZDFhMjIyZTI3ZTYifQ" + } + ] + }, + { + "id": "claim_0d00a794253149e8b320305c7295252c", + "statement": "The store refuses option-like host fields at write time (issue #101): `create_host` and `update_host` call `reject_option_like` on `name`, `address` and `username`, refusing any value starting with `-` that ssh would parse as a flag; `is_option_like` is exported so the host form can flag the value while the form is still open.", + "evidence": [ + { + "resource": "repo://src/store/hosts.rs#L204-L208", + "version": "repo-lines-v1:sha256:5517453c63a09976aa8daa9b037c75f5988e4a5691f77ccd1331c2c483816c29:eyJzZWxlY3RlZExpbmVDb3VudCI6NSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYmYyNTU3MTUzZGFmYWVlNThmYzMxNTgwZTViODdjZmZmMzgxZjY0MTliM2E0NmMwMGZjMjA5YmE3MWExZmE0NiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZmUxYjM3ZmEzNjgzNTAxYmVkY2U5MDU3NGY3NDM1YzMwZjljODc1YjE4M2I3MDRlNjIxMjZhM2Q5YTdkZjY2YyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMWYyNDA4YTU3YWJkMTJhY2VjZDA0OGRjNzBlNWI2YjYwZWYxMjRjNjcxOGIzMzhmNzk2M2E0MjI4OWYxMWJjNCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiYzNjOTllNDE3NTdjNWMxNWJkNzhjN2Y5YWQxZjE3MmY2NDBhZGJkZmI3ZmQxNzk1ZmE5ZjZiNTBhMGQwNGRhNiJ9" + }, + { + "resource": "repo://src/store/hosts.rs#L313-L319", + "version": "repo-lines-v1:sha256:3794aedd4d30b04db5cc46b7e053b6aa40e224baa67d50369ce91a1b8a709826:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZmYxMDgxNmE4MmFlMTRkZjI1MzllMTMwODBjZmVmODlmZWM5Y2ExZDZlNjVjZDUxMGE5ZTJhNGU5MDg3OTAwYiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZmUxYjM3ZmEzNjgzNTAxYmVkY2U5MDU3NGY3NDM1YzMwZjljODc1YjE4M2I3MDRlNjIxMjZhM2Q5YTdkZjY2YyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZDQ5YzU4ZmUzNDYxYjc2Yjk0ZGVkMjI3NmYzZDRhMzRjYmYxZTA3NTJiZTA2NWVhNGQ3ZDI5NzcwNzg0ZmMxMSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZjhjYTcyMjVjZDg0OTdkN2M3NGQ1YTZkN2RkNWJiZGRjYzM4NWM1OTU1NmVkOTQwOTFhYjJiZGMzMzg4N2I2ZiJ9" + }, + { + "resource": "repo://src/store/hosts.rs#L887-L910", + "version": "repo-lines-v1:sha256:2c9942e77b2b2c22ec28529cd519206744a9585b8236d598359faf88787a4eed:eyJzZWxlY3RlZExpbmVDb3VudCI6MjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjYwOTNiMTNkOWQ0ODNhZjFiNGRlNDIwN2QyYjYzMjg0Y2ZjODE3MDRlMjk4Njg2MWM3MWRkOWFmNDg1YWY0YWQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImEwYTEyNjk5YzE4MDk4MDg0Mjk3OTNhOTNkZGU2MDcwYTU4Y2VlMzBmZmIxMmQ4MTEyY2FjNjI0MmU0YTI3NjEifQ" + } + ] + }, + { + "id": "claim_6d9dc13c850a4cc2bff0f857f057fbe6", + "statement": "Leading-dash protection is layered end to end: third-party importers treat one store refusal as one dropped row (the poisoned entry is skipped, the rest of the file lands, counted in `HostImportReport.skipped_invalid`), and `safe_ssh_target` rewrites a leading-dash connection target to the `ssh://` form OpenSSH refuses outright at connect time, so a row written before the guard fails loudly instead of executing a local command.", + "evidence": [ + { + "resource": "repo://src/import/mod.rs#L16-L20", + "version": "repo-lines-v1:sha256:869b2cd056b9d48cbb44da0d6585a44c68f9d9cb23d7fc43a78debd2a56a4b50:eyJzZWxlY3RlZExpbmVDb3VudCI6NSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYzNmY2E0ZWY0MmFiNGQ5M2ZjNTZjZDA0YTRjZjM5NTEzMmNkNzI1MGY0MjAyODBjNTVlNGIyYjRiMzk3NDZjYyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZjkzZDJmZGNhYTQyYmY5OWZlZDExOGIxMjAzZDM4YWVlOTI4NDFlMTMyYTk5NjEwMzU2YjRiNmJjNmI1YmE1OSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjAsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZTNiMGM0NDI5OGZjMWMxNDlhZmJmNGM4OTk2ZmI5MjQyN2FlNDFlNDY0OWI5MzRjYTQ5NTk5MWI3ODUyYjg1NSJ9" + }, + { + "resource": "repo://src/import/mremoteng.rs#L449-L457", + "version": "repo-lines-v1:sha256:d4eabe1a0518c2c9ecbb692df8e84b968fb2a0dc8de9c508a8a03841a344a3b0:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMGU1ZTc4OGMxZjExMTlhZmNmMTQyOGI3ZTkwNGYyM2ZjMTM4ODNkNmY0YjM5MjZmMDM2YTNlMjdhOGY0NDU1YiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZTZiMDQ0OTE0Njk3MjJlODM2M2E5NGU0MDQ0NTM4ZTAxM2EyYTIxN2UzNmIxZTU2OGJhNjBhY2I3YjRjOGYyZSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMjM2YzQwOGZmZmY1N2RjMDFkZjY5Mjg0ZTZmYTczZWNmMDMwNDY4YmZhNjMwNTc0YWEwYmNmNzMwODk5ZjIzYiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiY2JjZjFjMjBkNGRkNWNmZDJjZDQxYTVlYmZlNjU0OTUzNjRiN2M0ZjE4MDcxYTc2MTgxZWRmOWRkMDQ3YmYzZCJ9" + }, + { + "resource": "repo://src/import/putty.rs#L392-L399", + "version": "repo-lines-v1:sha256:9d767c55d9d4f5dcf8c381a15f9102861c878a81e586919e65146d2000cc3cc5:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZDllZjdjNDYyMDUwNDc5ZDc4YmUxZjJiYWE4Mjg4MDMxMmU3ZGZlMGIwYjQ3Y2ExMjM0ZTFiMjljMGM1NjQ0NSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZTZiMDQ0OTE0Njk3MjJlODM2M2E5NGU0MDQ0NTM4ZTAxM2EyYTIxN2UzNmIxZTU2OGJhNjBhY2I3YjRjOGYyZSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiY2QzZjZhYzc4YWQ0YTM5MWE2MjgyNDIyNTNmN2NhNzk5MDJkMjYzODQ3NTY1NmUzYmJhOTFhYzAxMGU2NzFmNiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiY2JjZjFjMjBkNGRkNWNmZDJjZDQxYTVlYmZlNjU0OTUzNjRiN2M0ZjE4MDcxYTc2MTgxZWRmOWRkMDQ3YmYzZCJ9" + }, + { + "resource": "repo://src/ssh/host.rs#L88-L107", + "version": "repo-lines-v1:sha256:0855157bae5856cb010151d37ec33abc461233c80742b5d9cd9c0057cf0028b9:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRhZDMwNDVlODI0NDA2MmYyMzYwYTU3MzdmMTZmNDkxYmNmNDJlMjJkOTg5MWIyYTk1YmRhNTJlNTUxZWVkMGMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk3M2ZhMTMzYzY3NTU0MzE2NzIyZGI3YWNmNDI2NDAzNGNmZGRhMGQ5ZWNkZThhNWM0MjY3NjVkOTQxODA5YzciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImM1ODc1ZDMzNmI5NmVlMGNkZjdlNmJmYjg4YmU0NDRhYWQzY2MwMGZkZjg1Yjg3MmZiYjRkNTA2NTAyZTMyZWEifQ" + } + ] + }, + { + "id": "claim_7c4b2f0568034a49a68f3ca758b3f37b", + "statement": "The built-in Favorites group is identified by its `reserved` flag (`favorites_group_id` selects `WHERE reserved = 1`), never by name; when a user group already owns the name \"Favorites\", the migration creates the reserved group as \"Favorites (2)\" and leaves the user's group unrepurposed. Reserved groups cannot be renamed (rename is a no-op) or deleted.", + "evidence": [ + { + "resource": "repo://src/store/hosts.rs#L131-L140", + "version": "repo-lines-v1:sha256:370fba7fbe380814653c7cad48df38f6486535d3f2b737cf508e23972ee6b156:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNiNzYwNDgwMGRiODc0MThhZGIyMTJjZjY3N2I3NDM3YmIyMGFhN2NmYzgwY2U0YzFlMThjZTMyZTY3NjhlNTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImIxMmM1ZTYwZDIzOTljZmY2MGFjZDkxNWRjYmMxNjMwOTcxZjM0YTY1YTU2ODk2NWZhNDljODEwYzRkYTMyNWYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijg1MWNlNWU2Mjk0OWU0NmE1Y2Y4ZjZkMTYwZTc3MDkwNjdmZTk2NWM1YTkxNDVhMmIyNmM5Mzk4M2M1ZjE5NDEifQ" + }, + { + "resource": "repo://src/store/hosts.rs#L74-L106", + "version": "repo-lines-v1:sha256:0fa70c7ca39a370dc72e95ed4fde0302caf4b9b37329dad792300b1aa115646a:eyJzZWxlY3RlZExpbmVDb3VudCI6MzMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU4N2YwZmZhODY3NzVhMGI2ZDk5NzAwNzFjNTcwNzJiZWI1YjA0ZWJjN2ZhMTFmOTc0MDc4ZGE1ODUyMGI2ZjMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRhNTczMmM1NmIxNmIyNjJmOTU5ZjgzZGI0MmQyNWM0ZWY3MmM3ZTEwN2U5MWJkNGY0NDEyNTU3OTliNTJmMjciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjgxYWM3ZWFiOGY1NmMxMmJmNjNkNzhmZDA2NTIyODZiNGZjMWE2YjIwMjc5MjFkNThkOTkwZWU5YzYyY2NlNDgifQ" + }, + { + "resource": "repo://src/store/migrate.rs#L429-L453", + "version": "repo-lines-v1:sha256:c0e79c9842d6aa112a1658e1dc8042cae1f376c18e7ee3992532531b28c98eda:eyJzZWxlY3RlZExpbmVDb3VudCI6MjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFiNDJmZTZjZGY4ODNkMGMzOGNmZWU4NzRlMWFjNWY5NzY2OTQ5MjM5ZjI1ZTJlNjFlMjQyMWQ0YzBlZjMyYzQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFkOWUxMjU0NjQyNTU3NmQzZGVhZThjOWI5ZGJmMTI4ZDVkNmRkYWY1ZDdmZTg1NzkzNDkyN2RlZmZhM2I5ZGQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjgyYTNkZWZlMWYxMDI1YTE1NDU1NjM2YjM0ZWE5NjliZmE5ZTdhYjY1ZGFiOTJhMTg1MWNhNDdiMzdjZWQ2OTIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjBhOTJjNTcwYjg2N2Y0ZjkwOTUwMjUyZGZkZGVmMjg0ZmExN2NjNjJmZjM3NWNiMjJhNDllODEyZmQ1NzZkMGEifQ" + }, + { + "resource": "repo://src/store/migrate.rs#L843-L884", + "version": "repo-lines-v1:sha256:8649591610c048ce691d183aa17e32846526d7e17bbd49e85910a8c663157f4a:eyJzZWxlY3RlZExpbmVDb3VudCI6NDIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI5NDg2MzdhNmNmZDZlNzI3YWJiMzUyYmIwZWZkOGUyOGQxYTgzYTk3NzVhZmQ0ZjFhNWI4M2ZjNWU5ZThkOGMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImEwMGRiMDQwZjI5MDliZjk3OGZmMGE5NTZhNTVlODNjNjUzNDE4ZGU2YmI2OGY4MDQxZGE5NzZmOGQzZDIyNzAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImYwNTJjZmQ4ODg3ZWE2MzI4MGIyYWE4MmY4ZTlmNTc2MTBhNzRmYWZkYTZhNWZhYTA0NmM3ZmE0MjMyYzYxM2IifQ" + } + ] + }, + { + "id": "claim_bbc4adeb3d7c421b8b4ca20a394e8f80", + "statement": "The `favorite` flag and Favorites-group membership are kept consistent by `sync_favorite_membership` on every write path (favorite toggle, host updates, ssh_config upserts), `ManagedHost::favorite` is derived at load from reserved-group membership, and `set_host_groups` replaces only non-reserved memberships so saving the host form never drops favorite status.", + "evidence": [ + { + "resource": "repo://src/store/hosts.rs#L142-L165", + "version": "repo-lines-v1:sha256:0e39cacbf15cd1a1f03add62516751fe173dbbd5ef193c233a55d006b05a96ea:eyJzZWxlY3RlZExpbmVDb3VudCI6MjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE4ZTU0OTk2MDVhNWIxNmJlN2QyNTJjZmY1OTZmODc5N2UwMWU5MDUwYTlmODNkYjM3MTMwMzcyMzMzOWNlMTgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImEyOTBkZWVkYjU2MzVlMjhlYjBkMGE0NzJlYmFhYTNlZDI3MmI2NDBjMTljYTJhNzllM2JhNjk0Y2M0Njc4YTIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImViOWEwOGJjMjlmNmViZWJlOWE1ZjAxOTAzZDk4NDVkYmNiNTg0MmE0NTVmMDdkNWY0MDAyZGFjMzJhNWM2MDUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjRjYmM2ODhlNDk0YjVkNTkzZWJiYTJlMTk5MjM3N2FmYWRlZjQzZWYyNWE1MjA5YmYxMDM1OTQwNjdkZmY2NTcifQ" + }, + { + "resource": "repo://src/store/hosts.rs#L940-L944", + "version": "repo-lines-v1:sha256:c4578e5a345ff771f23903001b39cc7b84fba3200701faf4026d6b171f4572be:eyJzZWxlY3RlZExpbmVDb3VudCI6NSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZjhlNzczYmJlZWVmNTU1ZjBmYzA1YmRiZmE1MjRmMjRmZjI0ZTczOWY5NGViYTBhNmMxYjExNjQyMjU2ODk2OSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiY2JlOTYwZmI1ZDNlNWFhMzUyM2UwNzVlYWI3MDc2MDQyMDE5MzhmMGExMGQyZDMyOTMxZmE4NzBlN2ZmZjA3MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiODQ0NTRkYTBiMjVlMzFmM2EzYjdhYjg3OTA4MzdmODAxYjYwMzU3YzRjOTUzZjNkNTAzMDAwZjFlYTI0MmRmZCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiYTcwNDgzYmY5ZDhmMDQ5ZWQ0NjkwMWIxNjhmYzRmYzQyM2RmOGI3Njg1Mjg3OTBhMTRhMGE3ODA3ZGZlNGJhMSJ9" + }, + { + "resource": "repo://src/store/hosts.rs#L975-L993", + "version": "repo-lines-v1:sha256:24d864a69e4842ed1402d223892a3d51172e654228f85c7e286b32c24f606b2e:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImY5YzYwNDQyNDBhODg5N2U2ZDVhNDlmNDE5OThkYmY3YThkMjY0ZDViNjJjMjQ2YjY1OTdjMjYzZmQ2YzUxYjEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjY3NzUyY2M5NmU2OGZkMzI3NWJiNmJhOWRlMjIxMjc0MjU4Mjk3MjYxZDZiMjlhNWU1Yjg1NmYzZDc4MjUxY2UiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjVjYzViOGU1MmFmOWJiY2QzYTlmZTRhZWQ0MzBhNjZmNTk1YTM2ZWFlMDdjNjZkNmQxNWE0NGRkNjhkMzBhZGYifQ" + } + ] + }, + { + "id": "claim_ee68cde1f77645d2a10d733902825587", + "statement": "`materialize_identity` concretises an inherited group identity: when a host has a NULL `identity_id` and belongs to more than one non-reserved group, the primary group's `default_identity_id` is copied onto the host row so it stays stable across later membership changes; it is a no-op otherwise, and joining Favorites by favouriting never triggers it because only non-reserved groups are counted.", + "evidence": [ + { + "resource": "repo://src/store/hosts.rs#L1012-L1056", + "version": "repo-lines-v1:sha256:b9c005423957f5898b51d2fafc0b5d55ce8d8cd118751d393b8f99c17d3c1931:eyJzZWxlY3RlZExpbmVDb3VudCI6NDUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA1Zjg2ZDdhMjgwOTU4YTg0NDk2ZDRhOWQ1ODgzNzQyZjkxMjE2MjM1N2RjNWRkNWNlZDhmMDE4MTdkMWE0NmYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk1MDY5OTA4NWE1NzUxNTBkNjliYTFmNWU0MzVkYzUyNDIyY2VkNzFkYTI5YTI3NDYzYjRkM2RjNjI3OTc4ZGQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjU2YzczYTBlNDk1NGJkNDEwMjU2YTFjZmU5YTM2NzMxOTZmNzYzMTE2OTNjYmFmMjNiYmU2YzhjZjE0OTMzOTgifQ" + } + ] + }, + { + "id": "claim_a459e9749b234a179a9aa58cdcfdeb5b", + "statement": "An identity bundles name, username, private-key path, certificate path and `has_password`; a \"Default\" identity is seeded when the store opens, and deleting an identity that hosts still reference is refused (`DeleteIdentityOutcome::InUse` carrying the referencing host count).", + "evidence": [ + { + "resource": "repo://src/store/identities.rs#L13-L27", + "version": "repo-lines-v1:sha256:a3382edbad7aabbb5ccc01a54e301bfac7bf4820c6e10c54c0d2946dfd40e047:eyJzZWxlY3RlZExpbmVDb3VudCI6MTUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU3YmNiZjNkNDMxNzBmMGVlN2ZmYjI0OGM3NGVjOGQ1NzI3ZGI1YWFiNDQ5MmNhODk5NGI3MmVlMjk5OTdmMmMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQyNTE4MWNmOTdjNzZkOTY1Nzk5NzRiYTk1OGM0ZDk1N2QzY2Y1OGUxYWEyMTg0NGVkNGRiZDBlYmIyOWU4NzEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhiY2ZiYWFlNzdlYjIxYzJhZTJkYWU4Mjk0NDNkYmMwNjRiNzU3ZWYyMzEwNDAzY2VmMDBhNWUzMGNkYjA3M2QifQ" + }, + { + "resource": "repo://src/store/identities.rs#L151-L171", + "version": "repo-lines-v1:sha256:09c12a254342da96a259af010a2977d193f85a41946f02aaedccb2968df19384:eyJzZWxlY3RlZExpbmVDb3VudCI6MjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjY0YTNmZmJlMTU3MTc4OTM3ZjA2YjcwYzRhZGFlODkxZjdmODEyNjRiMWQ4YWFmNWY4YzY4YWJjZjI1MWQ0NGMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImViOWEwOGJjMjlmNmViZWJlOWE1ZjAxOTAzZDk4NDVkYmNiNTg0MmE0NTVmMDdkNWY0MDAyZGFjMzJhNWM2MDUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImJiYWM3OTZiZGQzNDZlYWU4ZTZmNjE5YWZjZGQ1NzU2Y2Q5ZTUxYjJkMGQzZjdiYjdlNTZhNjk0MzVmNTI3NmYifQ" + }, + { + "resource": "repo://src/store/types.rs#L19-L27", + "version": "repo-lines-v1:sha256:1dff0f5dbb065f93872cc7b710d24b44770bcdba9c4d83c7ca8c42c1242f7ead:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNDM3MGZlMjM4MjJmMzhlZmQxYjBlMjE2YTM5MjhhNTQ2MDdmY2EwMzFmODlkODZiMTJlODljMjYxMWQ5OGZhNyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNDQyNjc3M2IyNThiMjdkZDZhNDdjZGMzNWE3ODliZmY2MmY5Y2ZjMjg5NWY1MjU3MDFkMWU1OTA4ODU5NWM5MCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiYzQzMDg1NGI4MWJiZWU4Y2Y2MDg0ODI2OGJiYzIzMTJmYjA0N2ZkZTIyZDc4MzE5NWNkODA2YTIyMmQxYjdmNiJ9" + } + ] + }, + { + "id": "claim_832674ff6fef42cf8dd90fed914c5cdc", + "statement": "Identity passphrases and host passwords are keyed in the password store (OS keyring, with an owner-only file fallback) as `identity:{id}` and `host:{id}` respectively.", + "evidence": [ + { + "resource": "repo://src/credentials.rs#L214-L220", + "version": "repo-lines-v1:sha256:6561411fedc53691ca7b9e50e1068f8221a024e9e3c9730b911361f2222308f6:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiY2ExMzdjNzc5NmM4NmFlNWJlZjZkNzQwNzQxNGE0YWYyOTg2NmJkNzhjMzY1ZWNjNzJkNGJiMzUwOGRjMDU5ZSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMTllNzYzNTVjZjExOTk0ODBjM2Y5OTgzNmIxNDg2Yzg0MWQ4OTFmYWFkOTA0YzYwOGUwOTMwMjJkMzc1N2FlZSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiN2JjOThkNmIwZGM0OTJjNTAxNmRmMTA4MDVhM2E1NGViOWMxODRhNWE2MTM3ZTdmNGJlY2ZhMGMyMjA1NTBkMCJ9" + } + ] + }, + { + "id": "claim_b96fa6a0b75f428f8aa3b38f887002b0", + "statement": "ssh-agent integration wraps the `ssh-add` CLI: `detect_agent` reads `SSH_AUTH_SOCK` and parses `ssh-add -l` output into type/bits/fingerprint/comment, `add_key`/`remove_key` shell out to `ssh-add path` / `ssh-add -d`; the Keys tab exposes add/remove via `p`/`r` (plus `sshub identity agent-remove`), the agent panel is refreshed at most every 30 seconds, and zoomed it lists every loaded key as a selectable row removable with `d`.", + "evidence": [ + { + "resource": "repo://src/app/identities.rs#L4-L20", + "version": "repo-lines-v1:sha256:027f625b33ffb34c5d9d9fa146b009bf323e79c44f9f7f6b472900a2e0a4d9c0:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRiY2UwZmFlMzAyNTlhM2RmMzIwYWVlMmZkNmYxN2YxM2ZlZTk0MmI1ODY0YTZiNGRkZmJiZWI3ZTlhNGViMzkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjA5N2YxYjQyNTcyYzZmZTZiMjFhYzRmOTU4MWM3ODRiYjE3NTAxNTkxYjI2NGQ0Y2I0NWQyMjE2ZmVmZWI0MzMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImIxNDcxYWY0ZWYwNTVlMzYwNTBmNDkwNTcxMjQ3ZDEzYjIxZTY3NzFmNzhlYzA1MjhmZWY5YzE2ZDBlMzdiNzgifQ" + }, + { + "resource": "repo://src/keybinds.rs#L478-L480", + "version": "repo-lines-v1:sha256:cc06ebbb89388d0b18cfdf4d12b83e1ab02a600754c8e8ecd4f9d090c870427a:eyJzZWxlY3RlZExpbmVDb3VudCI6MywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZTUyZDI1ZWMzMmVlN2FkMTE5ODMwNWI5MDBjMTkzZjgzZGJmZmM3ZWViZDJhNDI4MzcyNTQ3MmExY2QxZDU1MSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZWQwMjFhZWIwMzBlZjBmZDZkZjY3ZjhlMGFkMmI1MGY5MDkxMTBmMzcwMmQ1OTVhMmFjM2FkOTIzYTI2NzFmMSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMzgxZWM1MDRmNjM3YmQxZmIxNDQwMzRjM2VkZTQ4NTQyNmE5MDRhMzUwOWJhNTRkMmE1NGIzZGVhNTA3NTQzMyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMWFlM2ZhYjg1ZDE0ZGZlMTdlYzYwOWU2ZWEzM2I2ZjIyYTI3OTQ5YzYyMzA4YjllZTE1ODg3YWI2ODNmNDQyZSJ9" + }, + { + "resource": "repo://src/ssh/agent.rs#L19-L49", + "version": "repo-lines-v1:sha256:43d2831e29e92ec65f60449581aba85548d708270ab889d5b27836fe9b8efcfa:eyJzZWxlY3RlZExpbmVDb3VudCI6MzEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk4YzQ5YTVmODQ4Zjc0ZjQ2MDA5N2RkNzg4ZjRjZTg1NDFlZDQ2YTc1YjBhN2QzNTBhYTlkZmJiZGE3MWI5NWYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjUzNjk0YjQ0NjE1MTE2NTE2ZGFhOGVkYjQ5YjMwMTA4Njk4MGJhMTJiNDIxODZmYjkwOWVhYzNkOGM0OTlhYTIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImE3ZDc2ZjhiZDIzYWI2MmQ4MzU0ZTIxMzRkNjc4MTE1MDBkNzJhOTY1MjQ3NTg0OTViNWFiMzM5MTIzMzdmNTAifQ" + }, + { + "resource": "repo://src/tui/widgets/middle_stack.rs#L456-L566", + "version": "repo-lines-v1:sha256:dbe63d587b0d6ba3f5e77a66a86348571f516ebb14818bedfbf82d27567034cd:eyJzZWxlY3RlZExpbmVDb3VudCI6MTExLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiJhOWZlYjBkNzI5YWUzMjY2YWMxYTc1OWRiZTJjOTM0NzQxYzM3NDQ2ZjlhOTBlYTBiNjk0ZmZjNDhiYjEyYmM2IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiIwMmI0MDViYTFhNGRkMjJjYmQ3NzkwYzA3MzcyZTQyN2E4YTM5NDkyYmZlNzQyMjBkODFhYmEyMTlkNzE2ZjRjIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI3MWQ3NTY5ZDFkMjNlN2I5OWM5NDJmMzhkNGQwYzhmZWRkODZlZWExNGEwYjdmNGMwODg1ODFhY2Q5Y2U1ZTc0IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiIxNzk5ZWE1ZWQzZWMxYjRmNTU3MzA4YTg5MzYyNTgzODJkZTlkY2UxZjU4NjI2NTVjMmVlNjc3ZTVhNDJmZGZlIn0" + } + ] + }, + { + "id": "claim_a2439ccc13784b46a9cae176679e53f8", + "statement": "Key-file probing runs `ssh-keygen -y` with the candidate passphrase staged through a `KeygenAskpass` helper — a `0600` secret file plus a `0700` script exported as `SSH_ASKPASS`, both removed on drop — so the passphrase never appears in process argv; `key_is_encrypted` and `passphrase_matches` fail open (return `None`) when ssh-keygen is missing or the error is unrelated to encryption, so callers never block on an unknown.", + "evidence": [ + { + "resource": "repo://src/ssh/keyfile.rs#L11-L104", + "version": "repo-lines-v1:sha256:285548f12b941e5ace0b6520be31e940a762ed20273951c039efe09b9575f910:eyJzZWxlY3RlZExpbmVDb3VudCI6OTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNjNjQyMGZmM2U4NzU0NDQyMjBjNmM5ZmM3MjIzOWZkYzg0YjEzZmFkOTQxODIyZDcwYzQ5YzQyNzRlNTFlODkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjIxZTJlM2I4NjU3MGJjM2JhOGQ3YTIwYWQ0NjUyM2JmMjVlNmYyMjAwYmU1NTZhOWJkNjA1ZjNmMTg1MzViZTQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjkyMzE4YjM0Njk3Njk4NDcxMzA3ZDA2ZGY0ODg4MmM0M2JlYWQ3OTdiNGMyZjNiYzlmY2U2ZTY0NjhhNGY1MjMifQ" + }, + { + "resource": "repo://src/ssh/keyfile.rs#L113-L129", + "version": "repo-lines-v1:sha256:ac008d53555adf5c4e0d8aad9b5d2121d57f1b327e876090876143bdd82f58d1:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijg3ZDJkZGM4OTdiOTA5OWU0ODAzZDkyYWQ4OGIxZDBkMWQzOWJkNjM5NzFhMjZmMDNmMjVhODBjOGQ3OTk5ZmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjAzYzVhZjliMWU4ZWExNTYyM2Q1ZmI0MmM3NTU3OWI3MGI2MTJjYTRmZGNhZDZlNWE1ZDkwYTI4MTA0ZmUyYTciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQzMjBlMDY0NGI3ODc3NWRmNGQwNjk4YWU5MzM4OWFkNWFlNjU5ZmRjM2M1Y2Q3MDQxODIzMGU2OGNmOGUxOGIifQ" + } + ] + }, + { + "id": "claim_0c3fc1decc3347d98477db676bc87bdd", + "statement": "Key generation (`g` on the Keys tab) offers Ed25519 or RSA-4096 with an optional passphrase, comment and target path; `generate_key_pair` refuses to overwrite an existing private key or its `.pub`, and on success the identity is registered immediately (named after the comment, else the filename), the Keys tab reloads with it selected, and a set passphrase is stored under `identity:{id}`.", + "evidence": [ + { + "resource": "repo://src/app/keygen.rs#L35-L122", + "version": "repo-lines-v1:sha256:d1ab4b93c73553e994345bf5219cc7fbf79c01924d46438f61dbc6242a0dca8b:eyJzZWxlY3RlZExpbmVDb3VudCI6ODgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjYyZjA3MmIyMmQwOGJlYmMyMzE5NTlmNzNiMjA1NWE0MzVkMjhiMmFkZTcwMjhkODM1YTAxYjAzNmQ0MjIyMjIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE2MmVlNTdlNDFhOGMyMGFlMDE2NGRmMjYxNDM5ZGNjNDk5ZDM1MjA4MzQxNTE1MTZkMDQ3ZThkZWFlOGE0NjYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI0MzYwNzUzMTFlZGI4M2U2OTBlNzZkMzgwYzk4ODU0ZjEyNGQwZDBhMDM0ZmZmMzNiMzgyMGRjMmMwMWQyMzIifQ" + }, + { + "resource": "repo://src/ssh/keyfile.rs#L131-L173", + "version": "repo-lines-v1:sha256:99ef61717ce83bc82040e37ed7f8d88bb80fa92a679656b22e48b3b7182e05ee:eyJzZWxlY3RlZExpbmVDb3VudCI6NDMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjlmYjA5Y2ZmYTAyNTZkZWQ4OGE5OGY0OTAwMmEzNzM2YmNjOGVlOThiYzQ5ZjQ4YmMzYWQ2NzkzYWRkZmVmZGIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImFkZTliZmM4MTljM2Y4NzRmZTYzMWEyNWMyYTE1NmIyMTY1MDM3NGU5ZTUwMTY1ODMyM2JkMTE1ODNjZDZmMzkifQ" + } + ] + }, + { + "id": "claim_1f274050ab364028a39606cca957886e", + "statement": "Public-key push has two entry points — `Shift+P` on the hosts list opens an identity picker, on the Keys tab it opens a host picker — and `trigger_push_key_from_hosts` calls `reload_identities` first because `self.identities` is lazily filled by the Keys/host/group forms; this 0.15.0 fix makes the picker open on the first press of a fresh start instead of bailing with \"no key identities\".", + "evidence": [ + { + "resource": "repo://src/app/push_key.rs#L10-L33", + "version": "repo-lines-v1:sha256:bca449d1ca7a57cd025c31426e7a8acf9aaa1de488065715523851d09c98b3b7:eyJzZWxlY3RlZExpbmVDb3VudCI6MjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk1Y2ExNTAxZTU4M2YyMzgzYzY2NTMxYTdjYTNiOTVlYzhmNGFkZTU3YmIzOWE1MTYwNWViMTczYjBmYTJhZTciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImNlMzAzMDEyMTY4Y2VlZGQ4YTFkMWE2ZWNhMDE3ZTE2YjU1MzZiOWFlZmU0ZTU5ZGNkOGJkYjk5MjhkN2I5ZTgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQ5OWY4YzdjZDU0NzJhMDU2ZTBlMDlhZjYwZDU0YmJlYTE4OWEzM2Y3MzU5NDBmYTNiMjdjMDE3NjhiZTEwZWMifQ" + }, + { + "resource": "repo://src/app/push_key.rs#L35-L52", + "version": "repo-lines-v1:sha256:93250498673439842e0b7599b6b232147ea264b13d34ce6850ae6076113b7118:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU0MjgzYWU5ZjA1OGZlMjI0ZWViMDY1OThhYzljNTYxN2Q1Mjc1ZjcwMTVhNjZhYjUzZTExMmIzMTdmZDhhYTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE2MmVlNTdlNDFhOGMyMGFlMDE2NGRmMjYxNDM5ZGNjNDk5ZDM1MjA4MzQxNTE1MTZkMDQ3ZThkZWFlOGE0NjYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjEwMjE3OTM0Yjk1YTM5NjBjZTFmYTllMzZiMmEwNjcxNTdmYjU2NjExZTExYzhlNGRhMjk5MGM1NDQzZTIwZGQifQ" + } + ] + }, + { + "id": "claim_f467cd68693246948db94f475385a83c", + "statement": "Pushing a public key reads it from the identity's `.pub` file when one exists, otherwise extracts it via `ssh-keygen -y` with the identity passphrase staged through askpass; the remote command runs under `umask 077`, creates `~/.ssh/authorized_keys` if missing, and appends only when the exact key line is absent, making the push idempotent. The push runs as a real PTY session reusing the stored-credential path, and both the launch and its failures are written to the audit log.", + "evidence": [ + { + "resource": "repo://src/app/push_key.rs#L196-L247", + "version": "repo-lines-v1:sha256:02bf22465839e3581cf4ad1030f0f688868097c7ed563281940d100ab4e50887:eyJzZWxlY3RlZExpbmVDb3VudCI6NTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjYwYTE1NGVkYjhhY2U4MWRiNGEzYTFiYTA4ZmZhMDUzODdmM2UyNDc4MWVhNDc1ZjY4MWQ4MmYyZmQ5MzNkZDgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImM4NDliMjM5NjBiZGIzNWQxOTlkMzhlOTRjODRjMjBkYzcyY2ZlYjE4MzAyZTg5NTY3N2Q2NDk2MGMxMzM0MGEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjA0OGJlZjY3ZTE1ZGUzNGIxYzhhYTJjZmJlNmRkZTIwMDZiN2VmYTE0NWUzNjVjYzcwOTNhNDJmODEwMTUxMzcifQ" + }, + { + "resource": "repo://src/app/push_key.rs#L322-L343", + "version": "repo-lines-v1:sha256:03370aa1fd91f61d12fccd541bf8f3b0cb622b9dfc77019d14428212cbb8f5c6:eyJzZWxlY3RlZExpbmVDb3VudCI6MjIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ4YmQxMjllNzgyNWNhNzk0NGNlNzY1Y2E0NjNhYjY0NTc1ZmZiYzk4MTBjNTA2MDRlMzQyYmIzNDk3OTgxOTAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjExNDE3MTJmYjVmYmMxMGMwNzY1ZDllN2FiYjRlOGZkOTYyZmMxOWU0NjBiMjEyMWZlZDFkNDU3MmUyYjllMjciLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImZhMDJlYTk0ZTZjNGZhNjI2M2U0M2E3ODQ2ODMxOTc5ODcyYjk5ZjNjZGQzN2JjNjZkYTA0NDRhZWYwYWIwN2UiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijc4NTE2MjJlYTM4MTIwZDI3NzNmYjI2Y2IwNmYxMzEwMjkzNjBiOTQ2MmIyMDNiMTk0MDA2ZWJiMmY3OTQwZjgifQ" + }, + { + "resource": "repo://src/ssh/keyfile.rs#L176-L202", + "version": "repo-lines-v1:sha256:9ed6a571cf93083d48127e9aaf9213cb4b8b1a6b1caf5dc258c3e9cbc0f6baeb:eyJzZWxlY3RlZExpbmVDb3VudCI6MjcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijg5ZTdmN2NhYWMwY2FiYTRmYWFjYTU4ZmNmZGI1NzI0MDZhZDY2ZjQ0ZTIwZDIxM2NiYTYyZThmYTNmYTc2MDIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImJlMzQzODlmZGY3MWRkZjI2YzYxZDY3ZmZjZjgwMTMyOTBmYzE4ZDUxMTk0NDAwZjc2ZWFlY2Y4MzA3YTU2NTciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImE4YmY5NWQyOTFiNTE5YzFiNTA3NzZkYTQ1ZTE0ZWFjNzI4NzZjZjE3MTFhM2NhMmYxMGEzN2RjMTZiZDNjMjQifQ" + } + ] + }, + { + "id": "claim_a9a310ad07aa4df39b7823e6f48550bd", + "statement": "`src/ssh/probe.rs` today only defines the `SshLogEntry`/`LogLevel` types fed by manual `push_ssh_log` calls from connect/session/push-key paths; its former background `ssh -v BatchMode` classifier (`spawn_ssh_probe`/`classify_line`) has no callers and was removed because periodic probes buried the user-initiated events in the SSH log, which is now capped at 200 entries.", + "evidence": [ + { + "resource": "repo://src/app/mod.rs#L1124-L1143", + "version": "repo-lines-v1:sha256:56e983319c86d8fee8781b238c738734422975798cc6588660371fc2f8f7aa89:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjZiYmQ1NjU1ODlmNTdkNzhiYTVjZDVkZWQ2Mjc4MmM3Yjg4ZjMzYTZhMTc5ZGNhMWFmOTZlM2FjZTQ4OTgxZmEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjhkY2EzOTUyZTI0MTJmMGIzYmE1NGNiYzdlMjBiMjMxZDQzMDBmZjIyMjRlZjVmYTExNzQ3NjU3M2ZlYWJiNTYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImVhNDQ1MDM2ZTZiNDMyYjcyMWI1MDhjMmIzM2MzNDgyMzk2ZjFiOWNlZmY3OGNmYmU4ODNlMzc2OWIxZjU1YzIifQ" + }, + { + "resource": "repo://src/ssh/probe.rs#L1-L16", + "version": "repo-lines-v1:sha256:3da87504304fa3a46ab7acd1999b6a4dfc54d3d9f4153d495aa9ffe049028484:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjVmMjQ4MTAwZTNhYjE4MDRhODNmY2ZmYTM5MzhkMjVlYzQ1Y2U3MzNlYTE4ODUxODQ5OTIxNThjMTM5ZmZhMmUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ" + } + ] + }, + { + "id": "claim_548e8667c2bf41cf8d9ba39f324e77d5", + "statement": "There is no live auth-method display in the host detail panel, but the connecting screen shows the server host-key fingerprint: the session parses `debug1: Server host key:` from ssh's `-v` stderr (first-wins, so a later forged line cannot replace it) and the connect screen renders it.", + "evidence": [ + { + "resource": "repo://src/known_hosts.rs#L240-L246", + "version": "repo-lines-v1:sha256:bd27467289c8f91370e648eb4cda7eabc25f0f25583ae2e78836c444b3cc46c0:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNDRmZjA2M2Y4MTNkZmQ1MTYwMDNkMzY1ZTdmNGFiYzM2NGQ1MGM1NGNkZmNlMmQwNDdiOWM3NDc2ZjEyOTVjYSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiOWY1NjdjNDc5YjZhN2IwZjljMWIxMjY1ZTdjOTg4YmMxN2IwZmZhOTdmZmM1M2NiYWE1MmVjYzYxMTM2YmFkYiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMzVmNmY0NjZlNTljZmMzMDcwM2M0NWY3MmFmNjVkZTU0YWMzNjNkMzU0Y2MxYTMzMzAzNTBhY2Q0NTI0MzRjNCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNDUzZTA2YmU3OWJkYjAxOTQ2ODcxMDZhZTc4NGRiMWZkNGNhZWFmNWY2ZDI0NzhiMjY5MmE1MDJlNzA5ZmRjOCJ9" + }, + { + "resource": "repo://src/session/mod.rs#L532-L543", + "version": "repo-lines-v1:sha256:2fa83a88ea4876dd0a5192230ca96314b52b1083fced4299b93c6d4991bb8b62:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU4MjVmOTI0NjcyNzNlMDk2Y2YxYjViZjdjOTQxNjYyZTMyZmE5ZmY3NTNjMDQyZmIwOWQ2MDBjZDZhYzU3N2EiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjhkYjMzOWMxN2Q0Y2M1NGU3MGM0ZmNmZWNiNjExYmI5ZTdkMzYzZjI1YjE4ZTA5MjNhMWQzMDZjYzk1OTI0ZTUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImVkYjMyZjYxMDhhMmNlNTBhZWE3NzI4NTEyMmJkZGRhYzZiMWRhMTU4NWJkNzhmYzVjOWVmMDBjMjFmZTZmMmIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImVlZDZhMDM4MTQyOGNkODk2MGYxZTI3OWU4NGU4ODMyOTI1YzM5YjA5ZjJmNWY1Mjg3ZjgyODNjOTE3YmVlMDEifQ" + }, + { + "resource": "repo://src/session/render.rs#L574-L585", + "version": "repo-lines-v1:sha256:2657566f3f2d598b91053ab2272d71d29d4efb6135cc12fade12ca213cb0c521:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjllNDA3MWU0NDU2ZmQ5ZDRlMmY4OWU3ZTQyMWYwYmExYzgyMzRjMmZlNzIxNGJkMTIwYjNiMWRhNTg5ZjFiN2UiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjExMDE0ZTMzYWRhZGM3M2IxOTQxOTk4NGViOTc2ZjgwOGZhNTM1MTFiZjQ4OWQ4ZjNmZDFjNjZmN2ViZWVmMzAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUwYzczNTBmODIyYmEyZmIyMDAzYTRkZTM5MjZjOTQ3OWQzMTIyZjJlYmJjNzllNDk0YmJiNTQ4YjhhNGUzYmQifQ" + } + ] + }, + { + "id": "claim_2aaff9cc62c84eac900884831b5f6208", + "statement": "OS auto-detection runs as a single background worker thread (same mpsc shape as the ping worker, test seam via the `ProbeRunner` trait) that shells `cat /etc/os-release 2>/dev/null || uname -s` with `ConnectTimeout=8` and `StrictHostKeyChecking=accept-new`, using `BatchMode=yes` when there is no stored secret and askpass-staged credentials when there is; an `OsDetectEvent::Detected` is emitted only when `parse_os` recognises a canonical id, and probe failures or unrecognised output stay silent — the icon remains empty and the probe is retried on the next connect.", + "evidence": [ + { + "resource": "repo://src/osinfo/detect.rs#L1-L143", + "version": "repo-lines-v1:sha256:342dd81ac7dcd574a3fc220e0ae8dfe1e36d34e07e4952ce2fdad0cfaa146009:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQzLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiIyZDQyOGNkZTJlM2Y1MGI0NGI5OWQ1ZTdhOWZiNzEwZGRlMDRjYTUwZjUxOWIxMjBlOWU0ZjBjYjRjMDQ3YTU3IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0MTJjYTM0NWNjZjc1YmY5YzA4MDZiY2U2OTViZThkZTgwOGI3OTk4NDI1MWE3YTU0ZDIwMmNmNjEwMWRkNDUxIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MCwicHJlY2VkaW5nQ29udGV4dEhhc2giOiJlM2IwYzQ0Mjk4ZmMxYzE0OWFmYmY0Yzg5OTZmYjkyNDI3YWU0MWU0NjQ5YjkzNGNhNDk1OTkxYjc4NTJiODU1IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiI3YmM5OGQ2YjBkYzQ5MmM1MDE2ZGYxMDgwNWEzYTU0ZWI5YzE4NGE1YTYxMzdlN2Y0YmVjZmEwYzIyMDU1MGQwIn0" + } + ] + }, + { + "id": "claim_73f5021c34eb4999b04395ba415350a3", + "statement": "On connect, only managed hosts with an empty `os_icon` and an id not already in the `os_detect_inflight` set get an `OsDetectCmd` (legacy/ssh_config hosts are skipped, and rapid re-connects cannot spawn duplicate probes); the UI tick drains the worker's receiver and `apply_os_detect` persists the canonical id to `hosts.os_icon`, clears the inflight marker and reloads hosts.", + "evidence": [ + { + "resource": "repo://src/app/connect.rs#L216-L258", + "version": "repo-lines-v1:sha256:fffee6e0bc82f78b10ff4cff2dd13f32c404104176f77368d58b17eaa93a5b9e:eyJzZWxlY3RlZExpbmVDb3VudCI6NDMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU3NGQ1MjkyOGI5MTBiZDI2OTRhZWIxZmFhYTU5NTA0NDdiYmJjZjVkN2QwNzUwMzRhNWFhMzYzOTVhMmE3ZTIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQyMzRiODNhZmYyNmNmMjUxYzg5NmY1M2ZiMTgyODcyZmQxODU2ZDg1OGRhZmE2Y2QxMDUzOTM0ZWY2Nzg5NjkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEifQ" + }, + { + "resource": "repo://src/lib.rs#L536-L544", + "version": "repo-lines-v1:sha256:4f1e1ac14833cf78d1fb7ff6a760e60b1104c174e8a48e267893dfb9085d783e:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNzc5ODM5NWI4Yjg2ZGFkOGNmNWMxYWEwZDg3OGQ2NDliYjc3ZTVmMmUxNWI5MDJjZTMxMGM4ZWYzZTc2NDgzNSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiOTk5YmRlZmVlYWNhNjM0MGUwMjkwZGM4NmU4ODljODlkNWUxODlhYTViYTQ0YjU4NWIzYjcwNzM0ZjRhNjJkOCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZWE2M2JmNzYxNjgyODdlYTEzZWZhYWJjN2RhNWRmYjgzNTJkY2I2YTEyOTlmNThiMzc3MTUxYTAyN2FkZjkxYiJ9" + } + ] + }, + { + "id": "claim_720bf4d79d43418d802b6adad567f2a1", + "statement": "`sshub import --from ssh|termius|putty|mremoteng` supports `--dry-run` for termius, putty and mremoteng — parsing the source and printing a preview without touching the store — and explicitly refuses `--dry-run` for `--from ssh`; PuTTY defaults its path to `~/.putty/sessions` when that directory exists.", + "evidence": [ + { + "resource": "repo://src/cli/inventory.rs#L47-L168", + "version": "repo-lines-v1:sha256:7f11fb039ca0cec78aef30a45252a32574631b516b43fc37aa3e25231dfe48d6:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIyLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiIzNGNhZmMzNTY3MzVlNmI5NGI4NWM0OGI3NzQ4YjYyODk2MmIyZWI1NTc0YjFlNjYzOGViOWI1ZGUzYzAwOTQ5IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0MTJjYTM0NWNjZjc1YmY5YzA4MDZiY2U2OTViZThkZTgwOGI3OTk4NDI1MWE3YTU0ZDIwMmNmNjEwMWRkNDUxIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiIzY2RiYzAzYmMxM2U4MDIyZDMzMDc2ZjA4MmYwZTIwZmE0N2JhMWNlNjkxNmM4YTZhZjQ0ZDMxNWNkZDQ3MzI4IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiIwOTBlNWQ5NjE0NTZkMzFlNjZiMmJiNTU4ZTA2OTA2MGM5OGViZThlOGE2MTE4OTkzYWFmMmE1Y2UxMTg5YmU4In0" + } + ] + }, + { + "id": "claim_34161fdd34834437924511ab1bdee586", + "statement": "The Termius importer parses `L00t.csv` plus an `ssh_keys/` directory, creating one identity per key (key material copied to `~/.ssh/termius_`) and hosts linked via the `SSH_Key` hint; every password and passphrase is stored with write-verification (`store_credential_verified` sets then reads back the value), and failures are counted in the report's `keyring_failures` instead of silently dropping secrets.", + "evidence": [ + { + "resource": "repo://src/import/termius_csv.rs#L211-L355", + "version": "repo-lines-v1:sha256:e761c604334023192d3f9d64401bd1547a47888c8f9c8b9870153755440e4f06:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQ1LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiJmMDVhNTJkMTA0MmNhNzFlOTcwYTBhY2UxYjUzYTYyMDgyNmJmMmYzNWZjZThmMDY1MWE5Y2EwZTVhY2Y1NWM3IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0MTJjYTM0NWNjZjc1YmY5YzA4MDZiY2U2OTViZThkZTgwOGI3OTk4NDI1MWE3YTU0ZDIwMmNmNjEwMWRkNDUxIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJiN2Q5MmZlNmY1MjZkMWI3ZDkwY2E1MTQ3YjJhYjFiNzVlY2Y1ZDEyMTYyZmZiZTk0YTUxM2M3NjYzMjg4ZWQ4IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiJjNGI5NDg2NjE5MTdiYjJlM2EwZTkzYWE1Y2JiM2ZlZWM3NWJlZGZmNmZmN2M0OTUxMDgwMDhiN2QzMzA0MTc3In0" + }, + { + "resource": "repo://src/import/termius_csv.rs#L361-L377", + "version": "repo-lines-v1:sha256:28501872a588f7aa56d29b2aaa2222605d92b6d8543a1a125631f02296aa2054:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRmZTY5NjZjNzc4ZjIwMzQ0YWE3NjIzMmY4MWI3N2Q0NDA4NjA3MTlkY2ZiNmQwZjVhOTgwODliNTg1OTdlNTAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImM3Mjc0ZTZlODIzNjgxYzNjNDNlYWY4MDc2ZDhjNTI0YTVkYmZjYmZhNmQ0ZGRiYjA4ZTZkNjUzNGU5N2Q1YzkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJjMzk2MTFiNWQ3MzBhMDFkNmFkNzc4ODk4Nzc1NjcwNjEzNDc2ODlhZjk0NDI2ZDIwNGVjZThhZjllMTI3NWIifQ" + } + ] + }, + { + "id": "claim_7afdc06b3e654ecbb441c5c528c6191b", + "statement": "Termius re-import is refresh-safe: an existing identity is re-pointed to the key material copied from this export and its stored passphrase is refreshed, and an existing host row keeps its fields but its stored password is re-stored from the CSV (covering a previous import whose keyring write never persisted).", + "evidence": [ + { + "resource": "repo://src/import/termius_csv.rs#L242-L256", + "version": "repo-lines-v1:sha256:1139f5a26f3c50be35b528b6b49f1ae83f9a668db5455373df823588680b2da6:eyJzZWxlY3RlZExpbmVDb3VudCI6MTUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk1NTI0OGVlYjNjNTY4ZjQxODc2MzFlMmVkMjg5NDFlOGRlNDFmNjA0YjRkMWQ5NmFmY2E5Y2RjNDA0N2YwMmEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNiNWFhMDc5NTExZGUyYzMyZGNiM2YzZmU4NDA3MjZjMjZjNjQ5MmMyOWYxZjk4ZTM5NjAxOGYzYjM1ZDcwOGEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5YjY4ZGE5ZGQ0YTE0MGEyYTVlZTE4MTAyYTYxYzQ0Yjg4ZWIwOWE1NjJlOGEyZThhNTE2ZWMzNGQ1YjQ1YjgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjI0NDYwYTE0MGE4NDE4N2EwMGI3MDBmMzFlNmYxNDEwYTA4Y2I0NGFjNTVlYzk4OTYwY2YwMmExMThiZTRhNzEifQ" + }, + { + "resource": "repo://src/import/termius_csv.rs#L289-L340", + "version": "repo-lines-v1:sha256:a685eba75ffe7dbf337cf0ea519da5bed171a1f789081c1cf8defbaf1e9e44c9:eyJzZWxlY3RlZExpbmVDb3VudCI6NTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU2ZjVmY2MzNWNkOTAyYjZmM2M2YzQxYjAwYWNjY2U4N2NiODIzZTY5OTA0NjFhZGU5ZmQ3Y2U2ZjJkNzE5ZDIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImM1Y2RlZjgwODVhZTc0YWI2MzZkNjQ1OWY1YzZkNWZiZmIwZGIyNjRmMWIwZDc1YmFjNTE5YzliM2Y2NzE3YWMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI4ZGI2NTdmNjg1OGU1ZGFhNDY1NmRjMjViYjVlNmM1ZmQ4YjQ0OThmZTUxNzNlM2RhMjc2MWUzYTU4NWRmM2UifQ" + } + ] + }, + { + "id": "claim_eba827f172864035a4f4a87d03331d9d", + "statement": "The PuTTY importer accepts either a Windows `regedit /e` `.reg` export (UTF-16LE BOM-decoded, `dword` ports) or a `~/.putty/sessions` directory (plain `Key=Value` files, decimal ports), percent-decodes session names, and imports only connections whose `Protocol` is `ssh` or absent; non-SSH entries are counted as skipped, the `Default Settings` pseudo-session (empty `HostName`) is dropped, and rows land as launcher hosts noted \"Imported from PuTTY\".", + "evidence": [ + { + "resource": "repo://src/import/putty.rs#L1-L19", + "version": "repo-lines-v1:sha256:9ba51c60fc501a5bf1ade0f433bf706ecc8747decbbd6f8666ae4296e6f3403c:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxOTlmYWZlZWViYWQ0NmExNTNkZmM4OGQ4YjNlZDNjNTYxMjZiY2FiM2I4MDZhYjc3MDRjNzYxNGJmYjcwYmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImUzMTA3ZDgzNGNkMDg4MGE2NTBiY2E3ZTIyYjIzYTc1NmRiYTBmMWU0ZGNhMzE1YmNjNGM1MGRkODlkOTNiNzMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImZlOWVjODA5YjAwNDRhMTE2OTNjZmNjZDkyOTI5MTI3NDFlMTMzZjQyNDI4N2Q0ODI4MDM4OGRkMTdiOWQyNDAifQ" + }, + { + "resource": "repo://src/import/putty.rs#L355-L403", + "version": "repo-lines-v1:sha256:4f3bc836a3be1bfd7745d91e813fdfe6a9f1ea337d590f3cbf41cb6549ce84cb:eyJzZWxlY3RlZExpbmVDb3VudCI6NDksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImI2ZTkxYWNiMzY5ZDI2ZTdjODNiMGFjYzVlMTc1ZDI1YTAwMTVkMjg1Zjk3MjVmOGQzOTJiMGRlOTg2OWU3YmEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImY5YmRkN2M3ZTQyMzU0NzA3YzQ1N2QyYjY4OTkzYTc3MmMwYjBhMmFjNDhmN2NhYmIxODgyY2ZlM2YzYmRlYWIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImM5MTgyNzhmNzVkYjAwMWJiMmQyYWFhNmRlYmU5ZmNjMzNjNDg4YmRmM2Q2MmQ0MDMwZDY1NTQxZWNlMzU5ZGMifQ" + } + ] + }, + { + "id": "claim_a5691ac91b954e169d96032f9ba16ace", + "statement": "The mRemoteNG importer parses `confCons.xml` with a hand-rolled byte-scanning XML reader (no XML crate): `Type=\"Container\"` nodes build the folder breadcrumb that becomes the host's tags, `Type=\"Connection\"` nodes with an `SSH*` protocol become hosts and every other protocol is counted as skipped; encrypted `Password` attributes are not decrypted and imported rows carry no stored secret.", + "evidence": [ + { + "resource": "repo://src/import/mremoteng.rs#L1-L24", + "version": "repo-lines-v1:sha256:aa2429ed6f5c21f4b332641a4248e25f71c9c12f66c6cd2ac7e46c0e4894e736:eyJzZWxlY3RlZExpbmVDb3VudCI6MjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVlZmQ3MmMzM2NkZjc0ZTM0YmIyZjRiYzE3ZjBmYmEzODVmMTljYjc3NTU2MTMzNDRkZGNjMTgxYjIxZjc2NTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjEyZTVkMDg4ZjAzNThiMjA4ZTkyNjE2ZjA0OGIzODI4MzlkYjkxMTE4ZTM3MzdjMGJmY2M0ZDhhM2UzNjRjMzgiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImZlOWVjODA5YjAwNDRhMTE2OTNjZmNjZDkyOTI5MTI3NDFlMTMzZjQyNDI4N2Q0ODI4MDM4OGRkMTdiOWQyNDAifQ" + }, + { + "resource": "repo://src/import/mremoteng.rs#L417-L461", + "version": "repo-lines-v1:sha256:6578b6e6d149ec4ce21e8bf1434c18333dfea1ef255a3a8e23e80fb9e6b9aa61:eyJzZWxlY3RlZExpbmVDb3VudCI6NDUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ0YmYyYWY4Zjk4ZjQzZDdmN2EwNjFiMDM1ZTgxM2Q0OGJiNGQ3MzI1ZThhYjQ3MDE5Mzg4OTU0Yjg0ZTlkN2IiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE1ZTM0MmQ1M2E0Y2UwZThlYTk3YWVlMDgzMDZlY2QzZWVjNDZkMWRiY2Q5MDRjZDU2YWVkMWRmZDdkM2E2NWYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImM5MTgyNzhmNzVkYjAwMWJiMmQyYWFhNmRlYmU5ZmNjMzNjNDg4YmRmM2Q2MmQ0MDMwZDY1NTQxZWNlMzU5ZGMifQ" + } + ] + }, + { + "id": "claim_024f4f5d5e1141cdb60f17ed99a43ef5", + "statement": "The PuTTY, mRemoteNG and Termius importers never overwrite hosts: a row whose name already exists is skipped (`skipped_existing`), non-SSH entries are counted separately (`skipped_non_ssh`), and store refusals are isolated per row (`skipped_invalid`) so one poisoned entry never costs the rest of the file; these importers create hosts only and carry no credential counters.", + "evidence": [ + { + "resource": "repo://src/import/mod.rs#L5-L20", + "version": "repo-lines-v1:sha256:e41761c56047ae1f3f537c7826181c2d2c0c36b78359a9d293efbd1c58aa07b9:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQyZThkYzNjZTI4ZjgzZjJhMzUxNDNmZDc0ZmUxZDMzZTU0ODBkMWVlMjdiMjliOTczMmY0NWU1NjFhNDBjZGUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjY1ZjhjZGNkNjM1NmQ3ZDJiMjc5MzVhZWYzNTU1NDAxMDg4MDAzMmJkMWE2NDljNjRjMmY2NmQyNWNhNDVjYTQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ" + }, + { + "resource": "repo://src/import/mremoteng.rs#L433-L448", + "version": "repo-lines-v1:sha256:48e651e4c3d90ba2d15ebd9f72aa871ce8ecb03b98e13fb7dfbb98d86509f262:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ4OWNlODcxYjk3ODQ3OGIxOGFjZjE5ZTI3MmUzMmVkZmRmMGEwOWZkYTY3YzA0YTBmNThjYjVhZGRlODAxODEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImM1Y2RlZjgwODVhZTc0YWI2MzZkNjQ1OWY1YzZkNWZiZmIwZGIyNjRmMWIwZDc1YmFjNTE5YzliM2Y2NzE3YWMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjlmZWVhMjlmNzUyNzU4ZGFkMTEzY2YxYjAzZTgxZTE5YzE3ZWE1MjcyZDgxMTdkODU3NWJiZTViMDU5ZmNkNmQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjNhNzYwZTVjZDkyOTI4N2M0YzE5OTRmNzg5OGQwZThiMjRjYWRhOTU0ZWUwYzJlYzRhZWVjY2M0NDBhNjliYTIifQ" + }, + { + "resource": "repo://src/import/putty.rs#L374-L391", + "version": "repo-lines-v1:sha256:728977a43fb29348da29d58f21c0a804295438b1a0e0d079725fb6220df14e21:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFjNjE2Mzc0NWI1MGQyYjU0YWI1NmFkODQxOWIxMjEzYjM3ODAyNTMzN2YzMTkwZWI2MDFhNzQ1NTViZmI2NWIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU5YzEzNTkwNjY1NjMyM2QxYzVlYTQxNjU0ZWU2NmFmYjU4NWFjZGNmYWVhY2ExMjFkNWUzMGJmMTkzZWVkZDciLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjlmZWVhMjlmNzUyNzU4ZGFkMTEzY2YxYjAzZTgxZTE5YzE3ZWE1MjcyZDgxMTdkODU3NWJiZTViMDU5ZmNkNmQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjU5YzYxNzljZWMwMjI4ZjFjNWU5ZDBlZGQ1NzM1YTQ1ZTg2NDJiZmFlYmMwYzJmYjhhY2UxYTQ4NjRmYjE2N2EifQ" + } + ] + }, + { + "id": "claim_b43b5953418c425798fa50d4660e5c66", + "statement": "Export never writes the user's own `~/.ssh/config`: launcher hosts are exported to `exported.conf` in the config directory using atomic replace with a `.bak` backup.", + "evidence": [ + { + "resource": "repo://src/ssh/export.rs#L11-L28", + "version": "repo-lines-v1:sha256:60a812877fcdb8425da9df7b4bd236471385b975796a1f7f88eb68967f55d13f:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImViMjcwMjc1NTk4NjVmMzZiNTc0YTNmYjkyN2RhZTA1MmRmMjQ5MDYxYzYwZGIwODQ3N2VlNjg5MTI4NTkwZjEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImI5NGFkZGM2M2Y3NDE1YWUxOGUyOTA5ZGE4ZjExNDM5YWI2NGM4ZDg2NThmODM1MjE3OGU4OTNhZGVmMWM3ZTAiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjcxMWI1MGRlOGFjYTg0N2Y4NDhhM2U4MmIxNzI0ZmI5OTMwZTIwNTA2NTQ3ZmM4MTU3OTMyODA2ZDE2ZDNkY2QiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjBlZTVkZjFlOThmYzIwZjUxY2M1MjllZmUwZGI0M2FkZDY0YzI3NGE1MzRlNmNmZjM5YWJkOWEyZjFkZjM2MjcifQ" + } + ] + }, + { + "id": "claim_a11745e71d9c4d09b870772cb8bf009e", + "statement": "The domain's core flows are pinned by focused tests: `tests/e2e/keychain.rs` covers the keygen form flow, successful generation and the push-key host/identity pickers including the group-header bail-out; `tests/smoke/cli_commands.rs` covers PuTTY and mRemoteNG CLI imports including a `--dry-run` that leaves the store untouched; `tests/e2e/termius_import.rs` covers the Shift+T import flow and connect-time password resolution.", + "evidence": [ + { + "resource": "repo://tests/e2e/keychain.rs#L166-L421", + "version": "repo-lines-v1:sha256:76262710c56f6e3d6ba3991381f044662575fb4a2b15a63d01c350b557371383:eyJzZWxlY3RlZExpbmVDb3VudCI6MjU2LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiIzNjBhNzA1ZjdjNzM4ZDU1MTAyMDlmZGVjMzFjM2ZmYWQ2NzJhNzU5MDlkOTM2YmZkMjNiZjczYWZlNTNmZGVjIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0MTJjYTM0NWNjZjc1YmY5YzA4MDZiY2U2OTViZThkZTgwOGI3OTk4NDI1MWE3YTU0ZDIwMmNmNjEwMWRkNDUxIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI3ZjlhZDQ2M2JlNzFjNWY0MjA5NDZhZmJmMDdkMGZlOWY1ZjExZGE4MmYxZTk0OTAzY2MzMzk1ZDhjMWEwYjM4IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MCwiZm9sbG93aW5nQ29udGV4dEhhc2giOiJlM2IwYzQ0Mjk4ZmMxYzE0OWFmYmY0Yzg5OTZmYjkyNDI3YWU0MWU0NjQ5YjkzNGNhNDk1OTkxYjc4NTJiODU1In0" + }, + { + "resource": "repo://tests/e2e/termius_import.rs#L50-L124", + "version": "repo-lines-v1:sha256:9f01f152e88b124454ee00f546f8e8c67895c01690432be77d8396e82e57a8cd:eyJzZWxlY3RlZExpbmVDb3VudCI6NzUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM1ODY2YWNlMjY0OTRmMDdiMmFmMjg4YzJhMzQxMzk0ZjMzYzFhYzU1NGIyYjdkMjMwMTY0MjMxNDdlYWY3ZDUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImYwZWViZDVkZWE0ZDQwMGM0MjA3NmM4NDc3OThiN2UzMjI5Y2M0YTcwZjEwMjhlNzNmMDgxMGIxODE5YjgyOGUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjdmOWFkNDYzYmU3MWM1ZjQyMDk0NmFmYmYwN2QwZmU5ZjVmMTFkYTgyZjFlOTQ5MDNjYzMzOTVkOGMxYTBiMzgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImNkNmUyMTk5NmM1ODRiZjMzZDkwZWU3MTcwYTk3MWU3YjY5Y2E0MzMxZjMyZWYwMGIyNWFiZWJiM2E4ZWMwMTAifQ" + }, + { + "resource": "repo://tests/smoke/cli_commands.rs#L286-L344", + "version": "repo-lines-v1:sha256:7cda0a6fee474161b0a75f06eb003b46d9a76e5fd50a08b2e74ed150dba0cdd5:eyJzZWxlY3RlZExpbmVDb3VudCI6NTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkwOGQwYjFhN2RjMWY3Zjc4MTIyMTMyZTczZDA4MWU4OWM3YTMwYWEzNzJiN2I2YmMxNzBhZWQ2NDdmY2YyZTAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQ3MDllYWUyMDFkM2VjNmNmMjAxNDNkZjY0OWQ1MjA4N2U0ZjRhZmZkNjc1ODcwMzE5NmM3ODNjNjk0MjU0ODYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQ4YjM4YWUxM2NkNDcxODQzNjdjYzJjM2NmZmIzNzEwZjViZGY5ZjFmMjliYjJkYTRhNmY4MGYzODM2M2U2YjUifQ" + } + ] + } + ], + "verification": { + "by": "openwiki/0.5.1", + "at": "2026-09-11T12:46:43.817Z" + } +} diff --git a/openwiki/.claims/integrations/external-terminals.json b/openwiki/.claims/integrations/external-terminals.json new file mode 100644 index 00000000..e4bbbc6f --- /dev/null +++ b/openwiki/.claims/integrations/external-terminals.json @@ -0,0 +1,344 @@ +{ + "schemaVersion": 1, + "pageVersion": "sha256:8074a69097ba96fa4937def252404db9d7a6dcbffa465127a746d3b66616263b", + "claims": [ + { + "id": "claim_5f1147cc787c4e158e72ce9152cb710c", + "statement": "The external-terminal launcher (TerminalLauncher for Kitty/Ghostty/custom commands) was removed in 0.10.0 and is fully deleted — no trait, no src/launcher/ directory, no test doubles; only the legacy `terminal` / `launch_command` config keys remain, silently ignored so old config.toml files keep loading (pinned by the parse_config_applies_overrides test in src/config.rs).", + "evidence": [ + { + "resource": "repo://CHANGELOG.md#L687-L693", + "version": "repo-lines-v1:sha256:fb5b3d5a5f321b39c39a926fd0a85a34e5290df1cfe901b7b67344b5e3d000b3:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMGM1MzhhMzQwY2I5MGI0MzYzZWYxOTk5ODAxY2YxMDAxMGZiNzBmMGE1NDJjODhkNGQ4NDkxYWVhZWFjYTE0MiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYTQ0ZWUyYTkwOWFkMTU2OGQwMjIwZWMxYjdkN2I1NGM2MWEzOTQ2NjY4ZmE2NTdjMWE0NDY3OWY4YjQ1YWU2MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNzA0ZjFhNzk2ODk1M2NmZDAyMmIyMmNiNGUyNmFkNDMyMDAyNGIwMDZkOTg0YzRhY2Q3MTllZWI4Mzg2MTQ2YyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiM2ExOTM2YWNjMDQ1Mjc2ZDBlYjZkYTA1YTRkZDhmOTMwMDBlYTY5MDQ4NjQ1ZDQyMDJiNWE4NjE4MjY4YWExMCJ9" + }, + { + "resource": "repo://src/config.rs#L1201-L1217", + "version": "repo-lines-v1:sha256:9c86261a3e742e2c3ed02e230d7ead07cf83af4cd23ba43323a33ff6933c3ab3:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkyYjVlZGY1OTQ5Y2ZlYTY3ODIyY2Y2OTExMzIzNjNkNjhkNmYxZTE2OWY1ZTBkNGY3YzE0ZTVmYTFlNmQ0ZTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjZkYjY1MTcwZmY0OTU0NzZjMzZmMmRiMzU1YjRiNGQ3NTEzYzY0YmYxODc5MDMwZTY5Nzk4ZjliYmJkYmNlNzUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImEzMjg2OTE1NTAyZmNmOTZhNTg3ZjVmNjMwYzRjOTE2Mzg3NzU5M2FjYzEyYThkMTFiMjEzMzMzY2Y5MDdjNmIifQ" + } + ] + }, + { + "id": "claim_0abf74860053413ca8aaa479c36815b1", + "statement": "npm/wrapper/bin/sshub.js resolves the platform-specific optional dependency (linux-x64, darwin-arm64, darwin-x64 mapped from process.platform/arch) via require.resolve, repairs a dropped executable bit (chmod 0o755 when X_OK fails), and spawns the binary with spawnSync using stdio inherit so the child gets the real TTY and shares the process group for Ctrl+C; it re-raises the child's death signal on itself and exits with the child's status.", + "evidence": [ + { + "resource": "repo://npm/wrapper/bin/sshub.js#L10-L74", + "version": "repo-lines-v1:sha256:fb19bfdfcd4a4113e870971b90e494bc29d1997545b9bb495e07d2d419c5d682:eyJzZWxlY3RlZExpbmVDb3VudCI6NjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjY2N2E4NGFjNTA4YmNhOWQ1N2ZlYjE3ZWYxZDU5YmUzYjA5ZjRlMzJkZTdhMDk0NTI2MWU5YzFhZmUyMGJjN2MiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRlYmRlZmM4ZTg1YjUzMzJkNzk2OTZkYmI5NGQwNmRkYTc2YmU2ZjJlYWUwYzYyM2RhZjVlM2E4MjJjYzFkZDMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQ2NWY0ZGYzNWNmNGU1M2RjYjNjYWQxYmM0YjFmMzA4NWQ3OTIwMTRhOGFkMTM0MTk2Mzc0ZmY4OThjZDBlNDkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRiOTY4NTI0ZGEyYWQzOWIyNzhmNmE4Yzk3ZjZkOTMwZDc0Y2JkYTI2NWI4ZTY0YWU2ZmVlMDdiMjRjOWE5YmUifQ" + }, + { + "resource": "repo://npm/wrapper/package.json.in#L1-L38", + "version": "repo-lines-v1:sha256:ec500f2c93e6d1bf1e1262a288675bd5902bead93f6c701c05fdd0aeaf42af2f:eyJzZWxlY3RlZExpbmVDb3VudCI6MzgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE2ZmIwOGZkYTFhY2I5NTdiNjExNmJkMzc4MTFhMWZlNDFhMDE2MTFjMDYzMWVkYmY3ODZkNjg4OWEyN2E1NWMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ" + } + ] + }, + { + "id": "claim_217ffbf4427249ffaf0198e2e9c390f9", + "statement": "The npm shim exports SSHUB_INSTALL_CHANNEL=npm because the shipped prebuilt binary is byte-identical to the release-tarball binary, so no path heuristic can distinguish the two install channels — the env marker is the only signal (also documented in tab_bar.rs's install_channel doc comment).", + "evidence": [ + { + "resource": "repo://npm/wrapper/bin/sshub.js#L57-L65", + "version": "repo-lines-v1:sha256:4e4bd2e0274d9651c6fcb2703a64b7902332f550da119975d310d8f3ff1ec8d6:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMjgwOWM5NTI4OGRkNmViYmNlNjczZTAzNWJlNDBjZjQwMDdhMWFhZjVmNjlkYzg4NmIxNThiYjk5OGM0ZDBhYiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiM2EzZTQ2Zjk0Mjc2YjZmNmY0ZWQxZjAyYWY3OGJlZjZhMmVlMWM0MzlkZDU1ODRiOTczOTQ4MzEzMjE0ZDJiNyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiOTdkODYzMmU1NGU5Mjk4YWJiOTU4NzI3ZTJkNTE0MDhhYmFmMTM2NjAzNTM3OTEwMTQwYWJmNjViMzBhYjYxMyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiOTJlOTk0NjgwMjAxZTQ2NDFjOGM0OWVkMzEzNTY2ZWVjZjNiYjMyNjcwYmRhMzMzZTBiYjBmMDNmYjgzNjI1MSJ9" + }, + { + "resource": "repo://src/tui/widgets/tab_bar.rs#L53-L65", + "version": "repo-lines-v1:sha256:01363ccfc6f8d260b94844e4a6d72f6f19d8a4aeec8bbbb0d7f5036ad22d56a2:eyJzZWxlY3RlZExpbmVDb3VudCI6MTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA2Nzg1NzllNmQ2YTlmMGFhMzZiMGI4YmE1ZjJkZTEwZWZkMWZjNTI1NjA0OGQ0ZmIyMzBjMDMwMGFhZmZlYTAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQxYWM0NDQ1MjI4MTU4YTMzYTdjY2RjZWU3ZDgxOWQ3ZDJmY2NkYjY2MDc5OTc5ZTY1ZDZmYWYwNDNiMzU3YjIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImFjODMyMzdhZDRlZDg5ODUwYmM3MGZlN2NhN2M3MGY5YjRlMGU5YWQ5MTBjYzBiYjdjMDdmZDdhMjI1ZDRlNTcifQ" + } + ] + }, + { + "id": "claim_d5d0f99e2e2a45dc8322b9a0e04b572d", + "statement": "Install-channel detection resolves most-specific-first: SSHUB_INSTALL_CHANNEL env value wins outright, then the exe living in $CARGO_HOME/bin (or ~/.cargo/bin) yields `cargo`, a build-target dir component (`target` or `…-target` from CARGO_TARGET_DIR) or ~/.local/bin yields `source`, and anything else (distro package, manual copy) yields no channel suffix.", + "evidence": [ + { + "resource": "repo://src/tui/widgets/tab_bar.rs#L12-L51", + "version": "repo-lines-v1:sha256:c205947e22b12e9d969912c2a2e7d66effdcfc88fa385ddb600fd1cd86111207:eyJzZWxlY3RlZExpbmVDb3VudCI6NDAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImY3OTcxYWFhZTk1NDQyNzM2ZDgyODdjNmVlNWJjNTQ0ZmEzNzJkYzA1MjNkZmY1YjA3YWI1ZTI0MDhlMWE5OWMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE2ZDc5YzFhOTQzMWY1YWNmYTczN2QzMjg5ZmY0NmFjYjhkOTU0MzNjZTEyNDk5ZWIzYzZlMzE2MWQ2YjE4ZjIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjdjODNkYWFkZTZkNGY1ZTdkNjA3NjlmOTM2MWRkMjgyN2ViMGIyZmNjY2ZmMGQ2NWZmMWNjZDA4Y2NjYzliNmQifQ" + }, + { + "resource": "repo://src/tui/widgets/tab_bar.rs#L53-L110", + "version": "repo-lines-v1:sha256:8188854879e21fcd11e417c0c88762bb244f05babc3fcd968a550588e961bfbe:eyJzZWxlY3RlZExpbmVDb3VudCI6NTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA2Nzg1NzllNmQ2YTlmMGFhMzZiMGI4YmE1ZjJkZTEwZWZkMWZjNTI1NjA0OGQ0ZmIyMzBjMDMwMGFhZmZlYTAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjU0ODcxOWUzNGJhNGFiMzU4YTAyODEyYTQ0MWIzYzY3ZWNmODg3NmYwMjZkNTc3M2I0ZDdlM2IyMzBlNjFmNjUifQ" + } + ] + }, + { + "id": "claim_f46026e5e451439e83e36776d3a3547e", + "statement": "npm/build.sh builds nothing: it downloads or copies the sshub-*.tar.gz assets attached to the vX.Y.Z GitHub release (or TARBALL_DIR artifacts in CI), extracts each into a platform package with bin/sshub chmod 755 plus rendered package.json/README/LICENSE, and verifies on Linux x86_64 that the extracted binary reports `sshub $version` and that the shim, driven like `npx sshub`, reaches the same binary and prints the same version.", + "evidence": [ + { + "resource": "repo://npm/build.sh#L1-L122", + "version": "repo-lines-v1:sha256:14a74d1d73c966a5c1c1eedf8757eae9aeda95b833cc1fbf184feb7723b5e476:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIyLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiIxZDk1ZmMwNGE4MGM5NTJmNDljZTQxODg2MjdjNTNiMGZiZThjNDQwNDFiOTUyZDU5MmFjZDFkZTk5ODYxNDY2IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiJkMTE3ZmEwMDZiYTkyMDg1MDBiMjkzMGNlNjljYmRlNDM2YzY0N2FmYTkxN2NiNzM5NmE5YmM5MTExYTQ2ZGQyIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MCwicHJlY2VkaW5nQ29udGV4dEhhc2giOiJlM2IwYzQ0Mjk4ZmMxYzE0OWFmYmY0Yzg5OTZmYjkyNDI3YWU0MWU0NjQ5YjkzNGNhNDk1OTkxYjc4NTJiODU1IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiJkNWI4NTczMDkwOWJkMmMzY2QzZDhkYzZhNzcxM2M0MjhjMDg2Y2Q1Y2U2YmU3MGJkNTEwY2RkOTc1YTRiNTkxIn0" + } + ] + }, + { + "id": "claim_8a4a04ea41a049fface91244f9add8a6", + "statement": "npm publishing order matters: the three platform packages (sshub-linux-x64, sshub-darwin-arm64, sshub-darwin-x64) publish first and the sshub-tui wrapper last, because the wrapper's optionalDependencies must already exist in the registry or the wrapper resolves to no binary; re-runs skip versions already in the registry instead of failing on immutable-version conflicts.", + "evidence": [ + { + "resource": "repo://npm/build.sh#L120-L142", + "version": "repo-lines-v1:sha256:5badb1db0b9964eb298803e4d593b51ef1aa26a1f0b97d9d09a832c7640bb7fc:eyJzZWxlY3RlZExpbmVDb3VudCI6MjMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjIyOTVkZGIzMzk0ZjdiZTI4YWQ1ODYyNGVjNWI0ZDA2ZThjODEwNDIwZTJkZjVhNzQzNDQyY2RjYmZjZTI1MGYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjMwODVjZTQxODIxZDVmZjFkYzBmMWM4MDJiMjZhMmI5OGNiNzZhZmMzYmFmYzgwMWYwMjExOGIzOGEyY2E3OWIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjAwOWUzN2NiNWUyN2FmNmEzZjRmM2MxODgwMTQ4NzBjM2FiMGMzYjU5OGFkODE4MDQ0ZDc5NDgyOTZhNzhiNDIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ" + }, + { + "resource": "repo://npm/build.sh#L46-L51", + "version": "repo-lines-v1:sha256:01e6612bfa75980286dd0b0901065daf15d3f17dd5e6923e6e2bc196ffce1343:eyJzZWxlY3RlZExpbmVDb3VudCI6NiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiY2ZjNmE2ODU3ODFkNDdhNWIzZWZhYzQ4Zjk4M2FlY2I2ZTQwZTJmZjYxODQ5N2YzNDViOWE3NGM3NWQ5NmZkZCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYWZhNDdlYjBiZDM0NmU0Y2ZkNGEwZTU2MDFlNGYyZDBjMzkxZDIzZDM3ZGFkZWE5NjNmNzhlZDZlNjI5ZTNjZCIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMTY1M2EyYzAxZDU5Mjc5Yzc4NzU4OTJhZDNhYmE4YWU4MjUyOGZjY2ZhMjk4YjQyNmNkMjhmM2ZhNDhlZmQ3MCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZjdhMzA0MTUyOGE1M2U0YWZkNzNjYTBmYjVlMGUxNjQxZGY5ZThlN2I3NGFlZGEyMjYwMTc2NDI1M2M3OTY1MSJ9" + }, + { + "resource": "repo://npm/wrapper/package.json.in#L33-L37", + "version": "repo-lines-v1:sha256:46a83426461e6d63628fc3cce3f01f7f560d3f22832f5b2aa1f1da409489772a:eyJzZWxlY3RlZExpbmVDb3VudCI6NSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiOGY0YmE2MWNkMGY5MjhlZjQ5MDkxODc3MTdlMTMwNTI1NTRiNDc2NmJhYzEzYmY1YjFiYmU1YzlhNzQ5NmFlZCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMjdlNTc4OGQ1ZDlkNzNkMjRhODFkYmMwYTVhNzRiZmE4MmM5YzNiM2FmOTllNTJhZjc5NmU5MGI0NjhhOGEzZCIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNjQwZTRmNTgxNTUzMTNjMGIyOWExYzM3MGI1ZGE3Y2YyYTI2MDUxOTBkMGZhNzdkNjhjZjVmYWE3M2YzMTYyOCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjEsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSJ9" + } + ] + }, + { + "id": "claim_dcea03070fa342a6a2e912e3db87321d", + "statement": "The release workflow publishes npm packages via OIDC trusted publishing (id-token: write, npm >= 11.5.1) rather than a long-lived NPM_TOKEN, with npm attaching provenance itself; each of the four packages requires a trusted publisher configured on npmjs.com pointing at release.yml, and crates.io publishing uses the CARGO_REGISTRY_TOKEN secret in a separate job.", + "evidence": [ + { + "resource": "repo://.github/workflows/release.yml#L87-L126", + "version": "repo-lines-v1:sha256:46d93b89478e40e7af1b36bfd6e606c31bd19bd8f4e209e91c8e05d8750a4db0:eyJzZWxlY3RlZExpbmVDb3VudCI6NDAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImYzMzE2Y2RjOTZhYzA2ZTA0YjFhYjI4Nzk3NWQ4ZGJhM2JhNmExZThhZWIwZTYwMzdkOGJjNWUzMTFkNmJhMTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjUxN2U2NTMzNThiOGRmMzEwM2Y0ZjM4NzBhNDA0ZThjNjMxZGQ0OTRmOWI0YjljN2RlYmRiYjgwM2YzZGEyZTUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjhiNzIyZWRkODE3ODM4ZDc4MjNiMzRhODU0OGYwMjFhNjc5MWY1MDcxNjk0OGM1ZjYxYmU2YmFhZTBhOWY2M2EiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImFmNzcyMTBkYmQyNmJmZjI4YzUwZWFiODAxMzNkMjM4OGJkYTk3ZmFiNDBhMTZhMWY3NzYwODc1MDQwMTAyMzEifQ" + }, + { + "resource": "repo://npm/wrapper/README.md#L1-L13", + "version": "repo-lines-v1:sha256:0327498d65bca2897127ef221f76cdebb7da7d96ad0dff6a57a52d321ca7f3ca:eyJzZWxlY3RlZExpbmVDb3VudCI6MTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjhlYmI2ZjI2ZWFjNThiODQ4OWMwODEzMzlkODAxNjU1YWFkZGU2YzY2NjE1M2FmMzZjMDM2MWQyMzAyNWZhNjAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjMyNjcwNGU0MmMxYzRlMDdlOTc1NzE3M2RmZGIzYjNmYmFkNmIxNmI2OTQzMjhmZDkzOTU3OWE5NTUxZGZiMzcifQ" + } + ] + }, + { + "id": "claim_8249314e1de14b7c84b2a02f9126ab91", + "statement": "demo/record.py records README GIFs and screenshots from the PTY byte stream (asciicast v2 events tagged with write time) rendered by agg, replacing VHS whose timer-sampled screenshots collapsed under SSHub's ~60fps animation redraws and produced sped-up recordings (measured 0.28x real time on navigate); the timeline is exact by construction and all takes play at 1.00x with animations included.", + "evidence": [ + { + "resource": "repo://CHANGELOG.md#L432-L446", + "version": "repo-lines-v1:sha256:a4d7b25dbc559463d2581d003be4499095aca6c82888ee7278c88210973bd325:eyJzZWxlY3RlZExpbmVDb3VudCI6MTUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImMwNDlhY2E1MzljNmVmNDczNmM0NTlkYWRmNDdkNDUyNzgyYzkwNzU0ZjJiMDhkMmIzOWUxNzhmZDU3MDRhOTkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjEyOTM3MjJjYzM5NDlkZWUwNmQ5ZDQ1ZWI3NDhjMTM0MzllNWM1NTViYmZmZDkwZWJiZGU4ZmZmZDY2MTJlMWYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImI1ZDdiODUzMTQ5OWRkM2Y3YzViNDIwNjZiMzJjMjVlMWU1NTUxOWNiNDc0ZTI2MjU2YWM4MDIzNDU4ZDExMGEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJmNjQwYTU4M2EwOTJkZWYyYWIzY2IxZWI3MDdlODc2NTcxMTU5MzgyZTgzM2VmYzUyODRmYmFiNjBjMDZlM2QifQ" + }, + { + "resource": "repo://demo/record.py#L1-L25", + "version": "repo-lines-v1:sha256:78b8ef36b0902c0a678261244ad20ffea30e2baedd992095393e20e5d2a33500:eyJzZWxlY3RlZExpbmVDb3VudCI6MjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ2ODJjYmRiNGM4YjA3NTE4YmY0ODZjNTg5OTBmYzM5MTc4M2FhMjBhOTE2YmZiMjdhZDIxMWViMWQ1YzM2NDIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImEzMDNlY2RkNGE0ZDEwZjE5YzczZmQ0MmZkMTI0MzdmZDMzNTRhODI2ODE2MDVkYTUyNTVhOTdhMzkwNjhkYmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjU3MzkxYzBlZDNlMmM1Y2VhODQ0YmMwOWYyZmM4Y2I1NjAxZjg5NmQxZTQ1NzE0MGUwODY1OTM3OTM5OTlhZjYifQ" + } + ] + }, + { + "id": "claim_ce688e245447469a8873f1bd1a3fca6c", + "statement": "Trimming a byte-stream demo recording cannot simply drop earlier events (ratatui writes cell diffs, so survivors would paint onto a never-drawn screen); Tape.begin() schedules a resync that narrows the PTY by one column, waits for the forced full repaint (src/lib.rs resizes all sessions on size change), restores the width, and the trim point is measured when the width is restored.", + "evidence": [ + { + "resource": "repo://demo/record.py#L108-L122", + "version": "repo-lines-v1:sha256:d44ec0c0a616e5fdab59652f9e14071d7f81e39c2708574d84d8003a89995ba9:eyJzZWxlY3RlZExpbmVDb3VudCI6MTUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImUzYzJlNjI2NTZiZDhlNjQ3YzNhYWU4ODgzNTZjMDliNjBmNzZlNTNlZGY0MDRiNjMzOWFhODkxZjljMDg5ZWUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImEyYWIwOTBiY2QxNjIwZmE2MmVhZWUxNTdhNzRmY2E2MzVhYTI5ZTEyY2JmZmQ2MThiNTc5MmQ4N2JjMDNhM2YiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImY0Y2EyYTRkNWNkMWI0OGVmNmFlYWQ0ZGQ4YmNkYmMzOTgxOTk2NDAxN2NjNzkxZTgxNTVkNjBjZTI4NGYwOGQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImMwNDdiMTBmN2MzMmM4NDU5NWNmNzY5YTRjM2Y5NWNjZWU0YTM5MTVmMjYxNTU5YWRlM2NhZDcyZTVkNmFkMjYifQ" + }, + { + "resource": "repo://demo/record.py#L166-L180", + "version": "repo-lines-v1:sha256:ff006b8be4303912987a38e774996021fe1d206687685519ee10d21281fb0e49:eyJzZWxlY3RlZExpbmVDb3VudCI6MTUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFjNzNkOTA5ODViODI5YzZlODBiMDdmYTg0YmE1NjQ0ZWUyYzA1MTc5ZTlkYTdiYzFmOWQ1YWJiZGQzMDkzZDEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjMwZTg2NmM4N2FkODFiZTk0MDZmZjExZTE5N2Y1OThkMmRhNjE0ZTMzMWE2OTgxNDU5YmY2OGIwNDAzZjhjZmEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImYyYTJiZTIzZjQ3NDQ2MzRiM2M5MjMwN2ViZGNkNzhhYjcxNWQ5ZDVjNmZmNWViZDU3M2MxMzE0MzZjNzQ2ZDkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImM3OTU1NzY4YWUzMmMyMjI3ODQ0MTYwM2NjZjBlOTQyMDY4Zjk5ZGUyNzRiZGE5OWNiZTdiZTNkODUyMDA0MjkifQ" + }, + { + "resource": "repo://src/lib.rs#L292-L325", + "version": "repo-lines-v1:sha256:b4d1ac42bb699079623923cc133beefb40a718a293cd8ecd216a91662599576e:eyJzZWxlY3RlZExpbmVDb3VudCI6MzQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImI5NGNhYTYxZDRmOTY2ZWQ2ODQ3NWM3OTQ4ZmY2NmM1Y2U3ZDMwZGQwNzNkNTdiNjc4MjM0NGM5N2Q3YjlkZjciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU1MjJjNDJjZDE5YzZkMDNkODA4YzQ1MjcyMDU1MjgyNWI3ZWExYzViNDkxYjIxODUxMWNhYzc0MTVjZjlkOWIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImY2ZTM1ZDAzNTNhNzZkMjAwNWZlM2NkYjI1MjJlNDA4OWNmZTZiODFjYjg5NDk3ZDgzM2I2YTlkYzdkMmMxYjYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk5MjM1N2JjY2NiY2RjMmY3YjZhMGU4MzUzNmU2MzhjZmUxOWFjMzAzZWM5YjQwMDc0NzExM2I4ZGNjODkzMTUifQ" + } + ] + }, + { + "id": "claim_f2284281bbfe4b919dee98649f30bee7", + "statement": "Demo still markers are scheduled like keystrokes but measured at record time (script-time drift would catch popups mid-slide), each reserving a beat afterwards to avoid sharing a timestamp with the next event; stills are extracted with ffmpeg as the last GIF frame at or before the mark (an idle screen emits no frames, so the next frame after a mark is whatever moved next), inheriting the GIF's 256-colour palette.", + "evidence": [ + { + "resource": "repo://demo/record.py#L124-L140", + "version": "repo-lines-v1:sha256:e56b622f47806f3ca058308bd84136aa016f4f0ba718b97d98f02508e3abba92:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2OWM5YmYwMDE4OWJhOTI0ODFmZmYxY2NlYTUyNDRhOTUwNzA0Y2Y5OWYwM2ZlMWRmOWFiYTQzNTUyZjk1NTIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImEyYWIwOTBiY2QxNjIwZmE2MmVhZWUxNTdhNzRmY2E2MzVhYTI5ZTEyY2JmZmQ2MThiNTc5MmQ4N2JjMDNhM2YiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImJhYzg0YzFkOTZlYTVjZGY4MjQ3YWNlMWM2NjJjNmNiNDJiYjBkNmI5NzcxYTgxZjNiODRiNTUwY2Y3ZWQzMWQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjVjZmRjNTA3OWZkNmJmZjRjMDI0OWE4NWRhNzQ0MWUyZmM0ZmQ5OTRjYTk0YzFlYmZhYzMzM2Y3NDRkMjFjZDkifQ" + }, + { + "resource": "repo://demo/record.py#L221-L238", + "version": "repo-lines-v1:sha256:59358fc11741bcc065f0e9e94dc1b43babb05dc33cfa62c85c0cd1b3767120a7:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjdiNWY2ZTAzMWM0ZjJhOTRlNmQ3MGE3Zjk0NzZhOTI2ODJkZmYxODQ1MjBmNDdmMjQwMThlZDQ5Njk0MmU2MzQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxM2E3NDk1ZGY5NWEwY2QwYmI1MmM0NDRhOWMwMmE0YzJlNDdlOGY2NTk1MDI2MGVmNGZkYTViN2QwMDg4NWYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNhMTZiNTVjM2E4ZDMwZWRjZjhmNTA4ZGYwYjI1NTM3MTIyMWUzOGJkZmM5ZTAyN2ZhOTZkNGI3YjVmYjMxMzMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk5NGRjZTgyODI3MjkyNWRmZGJhZjg4YzYwODYwMTkwNDc1OTNjNjAyM2M4YzUzNDk4ZDhkOWZlZmYyOTc4YTcifQ" + } + ] + }, + { + "id": "claim_270eaf06c43648b19a21bb28fbc1a928", + "statement": "Each demo take is checked against the pacing its scenario scripted: expected duration is the last scripted timestamp minus the trim start plus the 1.5s tail, and a take is ok only when recorded/expected stays within ±0.1 of 1.00x; an OFF take marks the run failed.", + "evidence": [ + { + "resource": "repo://demo/record.py#L426-L444", + "version": "repo-lines-v1:sha256:6f2b174d54de1f0f36a985a0c9235956eaf78e7124b923f76b03f5e599cc2da5:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBhYmM2Mjg3Y2M1NDJjNTZlODc4OGU5MzJlMDEzYmQ1N2ZhM2ZiNGYxNThlMTk4NWQ1ODRiNTFlNjAzMDFlOTkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNjNTEwMDY4MzBjNWM2MGU5MDc4NWQ5Yzk0MjM2MjMwMWIzZDBiZDVhMjBlMzcxMjdiMmUzOWIxYjM0NGEwMmYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjUyZjNjNDhlYTcyZThjOWNiYTllNzJjNDViNThlOGIyZjI0MDkzMjNmZmEyMjFmZmRiNTcxZmQ0YWFjM2RkOTQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjgyNzZiYjQxNzExNWQyYTljOWRjNThiMTUxYjJmMjFhMDIzMjk5ODc2ZTU5NDBmNDBiNTFmOTdiNmU3MzA3ZjQifQ" + } + ] + }, + { + "id": "claim_19b393924a3d4462867dedacb19445d1", + "statement": "Demo recordings run under a pristine per-scenario config dir (/tmp/sshub-demo-, copied from demo/home/.config/sshub) and a throwaway HOME (/tmp/sshub-demo-home) so the recorder's real username and home layout never appear in README media — the SFTP browser displays the local pane's cwd — with SSHUB_VERSION_LABEL set empty to hide the version and demo/bin prepended to PATH; motion stays on because disable_animation is the reduced-motion toggle that would hide every slide.", + "evidence": [ + { + "resource": "repo://demo/record.py#L241-L273", + "version": "repo-lines-v1:sha256:c1f3c310fb74885b73c9970009c3a4c1998bf647a3b3311aadb8bbc726043cfb:eyJzZWxlY3RlZExpbmVDb3VudCI6MzMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImEwZWFlYjkxNjZkNmU4NWM2NWNiZGUzYWY3M2Q5ZTBkZjM4OTZkZDg1MDU0ZGY4YTY1OGRmZWE1YmFhOGE0NDUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjUwNmZlNGE4Zjk2NjEyZGE3ZTE1N2UwODAwMGZjNjY1MjBjYjlmMWI2NDBkMzNlYTI4ODcyOTc1M2NlYWMwNjEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImZhZDYwOGZkYmUyMTNiMzQ1Mjg0Y2NhZTM3Y2RkNjkzODZlMjNlZjA1NmRkZmQ5YWU0M2E3ZTc0MjEyNzFlMDEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjY2NjVlZmNjNjBkZWNjZDYwNGQxZDNmNzlkOWY3OWY5YTI4NTBhMDkxOTc1MjdkYjFlMGNiMmY2MzFkNWRhNTEifQ" + }, + { + "resource": "repo://demo/record.py#L42-L49", + "version": "repo-lines-v1:sha256:5ca2946a66cca7debca54f2a5eb1d97babb1d6b4430686eb568d9edac3da1fab:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNDdiM2I3OGU4OGMyODUxMWI3OWRhNmU2MDEwZjljMTUwNzJlOWNhZjU2NzFkNTlhMzMwNzI2NWNhNTI3NGY5ZiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiOWVlYTQ4MjIwY2RlYzQ0MjQyNGEwYzdiMmJiODUzYTg5NTlkOThiZDE5ODQ0NTg5MzVjY2IzMWQ5YmI2NGIyZiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMjFlOWZlNmIzNGFiMDZlZDkyOTkwYmIyN2Y3NzYwOWNkMWJiMmJiNWMyOTJkZmYxZjY1MmZiZGY3ZDdjMzhmZSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiYjQxMGJkZWU3NjlhMjIyMDMyYjkwN2JmMTk5MjRkYTEwMTNjZDJjNjY4MGY0MzhhYjZiYjIxOGUzNjAzNDA3NyJ9" + } + ] + }, + { + "id": "claim_b77909b3ceb8429289028d302e866db0", + "statement": "demo/seed-demo.sh seeds demo/home/.local/share/sshub/launcher.db by running the seed-demo Cargo example (demo/seed_demo.rs, an example rather than a bin so cargo install never installs it); the seeder wipes launcher.db and metadata.db (plus -wal/-shm) for deterministic re-seeds and creates nested groups plus seven hosts using real pingable public anycast resolver addresses (Google, Cloudflare, Quad9, OpenDNS, Level3) so cards show online, while actual connects are simulated by the fake demo/bin/ssh.", + "evidence": [ + { + "resource": "repo://Cargo.toml#L28-L32", + "version": "repo-lines-v1:sha256:be7fa3a8b5e1da8681162a57bcdbdbf92e0033ee765b4f26320a144370ba62d1:eyJzZWxlY3RlZExpbmVDb3VudCI6NSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNjI2YWM3NWQ2ODU1ZTc4ZjJjZmU2MmQwNDFjYjQzZmU2ZGE1MDM2ZGI0ODNmOGU4ODY3OWIzODYxZDNlNGFmZCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiODA1MTdkOTFjOGY3MDYyZDIwNDk0YWZiOTdjYTk3NjVmZjdmZDhjYzYzYjE5NzNmOTQ2NzllZThkYmVmMjE3MiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZWU0ZjM4NzY2YzdkZGY1NGNmMGRhYTY4NmY3MzdlOTk4YTVkNmFlY2YwMDE4NzE0NDJjMzMwMDU0OWJhZjI3NiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiYjdmMzhiODZkOTZiNmMxOWE0NWRlNjRlOTVlZWM4MTMyNTRmMTE0ZjJkMjI1OTYwZDJkNjg0ODU1MDdiMjIyZCJ9" + }, + { + "resource": "repo://demo/seed-demo.sh#L1-L18", + "version": "repo-lines-v1:sha256:5cb4789978c5f27eacd4c77217f2758aa583ce60653b829c9aa7b9647154b66c:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFkOTVmYzA0YTgwYzk1MmY0OWNlNDE4ODYyN2M1M2IwZmJlOGM0NDA0MWI5NTJkNTkyYWNkMWRlOTk4NjE0NjYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjE5NGEzYmI5ZGNjN2Y3N2I5MjRjMjVmMGU2OTUyYjM2YzYwNTAxZTRkNzdlOGU4YWUzMmQxNzAzY2Y4OTdlZTYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ" + }, + { + "resource": "repo://demo/seed_demo.rs#L1-L8", + "version": "repo-lines-v1:sha256:c2bc0548ae9e0d596297b89bdbda40cf4909f638a2a4b1b8f9c4937995bc6cf5:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNWU4MzA1OWUzOWVkZTk2YWQ4YmEwN2U5NDAwYzVkYzdlNmUzZTNmYWM4OWU4NTZmNWNiNjYzZjE2MmE4OGUzNyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMDFiYTQ3MTljODBiNmZlOTExYjA5MWE3YzA1MTI0YjY0ZWVlY2U5NjRlMDljMDU4ZWY4Zjk4MDVkYWNhNTQ2YiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjAsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZTNiMGM0NDI5OGZjMWMxNDlhZmJmNGM4OTk2ZmI5MjQyN2FlNDFlNDY0OWI5MzRjYTQ5NTk5MWI3ODUyYjg1NSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiM2UxZmQ4NzkxOWRiMDUxYmExMjg2NTBkOTc4MDlmZmQzMzkwNjMzMTk2Njg3YTM4NjU4OGE1OGY2ODM0NjkxYSJ9" + }, + { + "resource": "repo://demo/seed_demo.rs#L28-L45", + "version": "repo-lines-v1:sha256:3dc3fe2535864ca65f8cc4de6dbb26afc8e68a3caff030e3a3d9edef5e9d56b8:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNjZDRkNjlkMTEwNDNjNmNkOTNjNGJjNmE5YWQ1YjRlODExZDgyNzdkOTI0NTVlZTZkMWU2NjY0MGU5ZWI1M2UiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFkNTI3OWEyMDZjZmZjNDBmODAyMzRhNjNlOGI3MmQ0YzA4YWYyMzhhMzRjMGZmNDA2NjM1MDBlODhmMzRkMTUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjhlMmRjMGZjMDM1YTc4OWJjNGYxM2Y3ZTMwMGYzMjY2NDRhNTMyMmM4MWRjMjBkZjc0N2YyYWU2N2FiODdlZDkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE3ZGIyNTA3MGRkOWI2MjU2NDVmYjcwZjI1ZWRiNjAyZTYwZjk5MmU1YTM1NGU1ZjJlZjdjMjNmOGI2ZDQ1NTIifQ" + } + ] + }, + { + "id": "claim_0eacd60e8e944af19e4d9a9e793f076a", + "statement": "demo/sftp-server.sh starts a rootless sshd on 127.0.0.1:2222 (SSHUB_SFTP_DEMO_PORT overridable) with an ephemeral ed25519 host key and client key under demo/home/.ssh, pubkey-only auth, and ForceCommand internal-sftp -d so logins land in a curated fake deploy tree instead of the recorder's HOME; because the host key is ephemeral, start purges the stale [127.0.0.1]:port known_hosts line via ssh-keygen -R so trust-on-first-use re-records it instead of tripping the host-key-changed (MITM) guard.", + "evidence": [ + { + "resource": "repo://demo/sftp-server.sh#L1-L13", + "version": "repo-lines-v1:sha256:b885bdb18d66edc9d50ddec55e4e79f43a0d28ad8698a4ecffe4b0266af8c099:eyJzZWxlY3RlZExpbmVDb3VudCI6MTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFkOTVmYzA0YTgwYzk1MmY0OWNlNDE4ODYyN2M1M2IwZmJlOGM0NDA0MWI5NTJkNTkyYWNkMWRlOTk4NjE0NjYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA2NzJmOTM2MTI5ZDZmMWI2YzEzMmEwNDE0MjVmOTZmNjEzNWFlZGVkNmM3ZTk4YzU5YTkyZjFhNWVkNTRmZGUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImJkYzhiNDI1NzgyODAxNWU3ZGRlOTE3MjFlYjdlMTZiY2ZlNWEwYzFlNmU1YjE1NGU3ODljY2U5ODExNTIzMmUifQ" + }, + { + "resource": "repo://demo/sftp-server.sh#L59-L93", + "version": "repo-lines-v1:sha256:8e46bf142ff32570c9384577ec2114edac704aa60c8c1283b369cbf4f4065df4:eyJzZWxlY3RlZExpbmVDb3VudCI6MzUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVkNTU1OTM1YTgxNmQ4NTk5MTA4MGNlZWM3NDA4YzQyMDc0MTkzNTllZTljNGU2YTc5OGEwZTczNDdhNWEyNmEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE1MzRiYWU5OGVmM2UxOWVmMDA2ZTUxODVlNzA4ZGU5N2JjYzgzNDkwODA4NTE0YjZlYTQwYTliZjE3Y2UxNzciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJmZGNhNTdmMjdkZTlhNDAzNWMzYjY2NTJkNGQyMDczZWVhYjEzNDFlNGVlNzkyYjcwODEyODMxYmViZmZmZWUifQ" + } + ] + }, + { + "id": "claim_4861140e2e0241ff84437247d06b3615", + "statement": "demo/bin/ssh shadows the real ssh during recordings and demo/play.sh: it emits an ssh -v-style handshake on stderr — including the Authenticated to marker that session code latches via CONNECTED_NEEDLES as the only honest connected signal — then runs interactive bash with an rcfile faking the remote box (fake uname/hostname/ls/df/uptime, deploy@ prompt) and redirects bash's stderr onto the PTY so the prompt is visible; it must never be installed outside demos.", + "evidence": [ + { + "resource": "repo://demo/bin/ssh#L1-L30", + "version": "repo-lines-v1:sha256:e1702dffdf90b026291094e903dd744ccf5f8467c936443c6cbc3e71b8fc47fe:eyJzZWxlY3RlZExpbmVDb3VudCI6MzAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFkOTVmYzA0YTgwYzk1MmY0OWNlNDE4ODYyN2M1M2IwZmJlOGM0NDA0MWI5NTJkNTkyYWNkMWRlOTk4NjE0NjYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNlODAzMTZlZGZlMmZjMTMxYmYxODA0MzBiOTk5Y2VmOTgxOGZmODhjMzQ2MmU4ZjA2MjZhZjRjMzFkMGVhNmEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImY1MGRlNDBiYjQ1NTVhMmRmZTViZjQ5NzEzNDU5OTI3NzFlMmU2ZDVmOWVhZDdmODA3YzBmMTI5ZTMwZTY2OGEifQ" + }, + { + "resource": "repo://src/session/mod.rs#L1003-L1006", + "version": "repo-lines-v1:sha256:2c6b898e2a8d693b53cc5a017753b3b14cf1ac6173baf429571c572fac9e7df6:eyJzZWxlY3RlZExpbmVDb3VudCI6NCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiN2YwYWMzMjZmMjFlYTc1N2MxNTYwNWM1NDlkNzI4MWFjZjA0MTFjNmEyY2U0MmU1NDgzMWM0NDk3OGUxNTUzMyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYWQ0MjcxMmU5YWQwY2JhYmQ0OGRiYTMyOWEwZjVhMmEwOTMxYTg1YjhhNjZlMDU5YWI5NGIzZjQ4ZjAzNWY2NCIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMTc1ODA5Yjk2NTk1MGVlY2YxZTIwOWZiZjliZDM0MmJmM2EzMWI4MjZhMTRjMDg5YWNlNjk5NGNmZTZlYjllMyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiM2M3ZDE3MTc0Njk3YWE2ODAxOTZmMzE1N2UwOTYwMzc0ZGM0NmYxZGQ2NDRmNzM3NTM1MGM4OWIxMTMzNjBiZSJ9" + }, + { + "resource": "repo://src/session/mod.rs#L747-L770", + "version": "repo-lines-v1:sha256:bc52c334317a3c66700c6da7688b9386d1edb1128873ab9024f5ca9e2f52e558:eyJzZWxlY3RlZExpbmVDb3VudCI6MjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjIzYzhkODhlODY5ZTNhOWI0MzBkZDFkYjQ5N2ViM2E3Mzk4YTc3NDk1NTYwNjIwMmQ2MWE2ZDJlYTliZTVkNmEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjVlZTExMzk0NjEyYjY1ZDVkMDgzNzFiMGM2NzA2NGNjN2U4YTI2ZmY4NTlhZjExMTgwYjk1ODQwYzE0YTRiMmQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjUzMzlhMGEyN2I1MDA0MzdjOTI1ZTViZWJiZTllNzI4M2UyNjVhMDk0MWE5OWE1OTNlYWY3M2IzNmMwNTUxYjEifQ" + } + ] + }, + { + "id": "claim_278088381afc44d69af6db24368754d6", + "statement": "The demo tree is contributor/release tooling, not product runtime: scenarios are hero, navigate, connect, add-host, sftp (driving the sftp-server helper via setup/teardown and running with cwd sftp-local) and screenshots (stills only, scratch GIF); generated GIFs live in demo/gifs/, stills in demo/screenshots/, per-take asciicasts in demo/build/, and the whole demo tree plus media are excluded from the published crate.", + "evidence": [ + { + "resource": "repo://Cargo.toml#L11-L22", + "version": "repo-lines-v1:sha256:386bc4e591a10e6228d1250198f96cf6703b21bbad6e01b473018013a5f2c222:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRkYWZhMjYwYmQ0MGFlY2ZkMGFiMDI1OGUwMDkyNWY4ZmUwNzEyYTIwZWIxNjY3NzI2YzU4Y2EwZGI1ODI0OGUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImY5NDE2ZTA5Zjc4YmEzMTZjMDMyNTY4MjEzYjg1NWVlNzU2ZTQxNTY1ZDkxOGYwOTUxOWM4MWZkNjIwZjQwNjAiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5YmFhM2RkZTkyM2JmZWM3NzExYjQ0MDY1ZmQ2NDJjMjY3NDYyZjYyZDFlYTY4MWFhYWE3ZjZlOGFjMTRkM2MiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijc3MWZmMjA1MjNjMjYxY2RhNmNkYTFiYmY4ZGZiOTZmNDk0ZmJlYzk0ZDM3NmYyODdiMTUyOGYwYWY1NDIxNDEifQ" + }, + { + "resource": "repo://demo/.gitignore#L1-L9", + "version": "repo-lines-v1:sha256:de1e85d2ec5698a20a3521d54505c9e7aba046973d83f4baecc04efe27bed631:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYTU4ZDJhZWZhYjM5MzExODhkYjgwMDJkNGJiZDhlNGJkMTEzYzljYmQ2ZjBhMDk2MGNjZmQ3ZDEwZWFlNTI1YyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZDBlOTYzNzdjZDhlNzEyNjQ5NjJmZTI2M2JkZGYyNTQ0N2EzNzk5MGQ2ZGI1Nzg4NDU3MTRjZDAyOGExNTZiNSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjAsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZTNiMGM0NDI5OGZjMWMxNDlhZmJmNGM4OTk2ZmI5MjQyN2FlNDFlNDY0OWI5MzRjYTQ5NTk5MWI3ODUyYjg1NSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjAsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZTNiMGM0NDI5OGZjMWMxNDlhZmJmNGM4OTk2ZmI5MjQyN2FlNDFlNDY0OWI5MzRjYTQ5NTk5MWI3ODUyYjg1NSJ9" + }, + { + "resource": "repo://demo/record.py#L391-L403", + "version": "repo-lines-v1:sha256:3fcebe34be839fcf331a68b56bd4079f9e3d124043ec5ab38f3af99c1ffbd991:eyJzZWxlY3RlZExpbmVDb3VudCI6MTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImMzMTMxMjU1OTIxM2U4NGE3YmZjMjFlMGIxNTkyZDZjMzlkZTE1MDVhMzM3NzQ5ZmNlNTdmZjBhMGM5MTQ0NjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImFhNDE1NjdiYWFkYjY2MDE1Yjk5MWEwYjcxM2QxMGU4OWNiNDU2MjNmNjYzZmIzZmI4YTUxYTkxYzU0ZWMyMDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjYyZGFiYWE5NzA2Y2I2YTMwY2NjZjQxM2NiYTc5OGEzYmQxNWI3YTA2MGFjNTczOGM2ZmQyMWM0Y2FjNmJjODYifQ" + } + ] + }, + { + "id": "claim_5e12a2d3407547fcb2984f4420f0ad4a", + "statement": "The OS keyring integration exposes a PasswordStore trait seam with OsKeyring over the keyring crate (service \"sshub\"); check_keyring_available probes availability and distinguishes a missing Secret Service provider (org.freedesktop.secrets / ServiceUnknown errors) from other failures, and where no keyring exists the owner-only FilePasswordStore (credentials.json written through a 0600 temp file with atomic rename) takes over, migrating back into the keyring only after every entry succeeds (migrate_fallback_to_keyring).", + "evidence": [ + { + "resource": "repo://src/credentials.rs#L113-L177", + "version": "repo-lines-v1:sha256:d5a518b043723d49043179255473d7d13fea02216d4f96ae078cdfcb1a8a3eab:eyJzZWxlY3RlZExpbmVDb3VudCI6NjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjZjYzg1YTE3OWRkMDhiZWUzNWU5MDBhMzc0NjhiOTVjMmY4NzBlZWU4ZGQxODQ3ZDFhYzE4ZGFjMjgwOWQxZTgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJhMmYyYjhiNTMxZDg1NTY2ZjJiNGQ5NzBlYmVmMzNmMzcwZTVkNzQxMTVjOGRkN2NlYWY2MmFlYzI3ODdkZTcifQ" + }, + { + "resource": "repo://src/credentials.rs#L46-L62", + "version": "repo-lines-v1:sha256:dbd0a676c1511f1dafe39a36d968e95334a583dd59af51050fac2e6e2033428f:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjhmM2JjYmQ4MjUxN2M0YTY1NDVmMWRmMTdhOWQ1MWVmMmJiZDc3MjczNzZlYWFiN2E4MWJjNmVmMjEzMTE0ZTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjU0YTFjZjhiZDczZGNkMzAxNTRkYzZiMjE1OGY1NWY0NWVlOWZiMGFkNjY1NjJmNmJmYzVhM2Q5NGZiNmYzYzYifQ" + } + ] + }, + { + "id": "claim_20917182967d4832953af88044bdee3e", + "statement": "ssh-agent integration wraps SSH_AUTH_SOCK detection and ssh-add -l/-d invocations, parsing the listing into type/bits/fingerprint/comment; the Keys tab and CLI expose add/remove key operations on top of these wrappers.", + "evidence": [ + { + "resource": "repo://src/ssh/agent.rs#L19-L49", + "version": "repo-lines-v1:sha256:43d2831e29e92ec65f60449581aba85548d708270ab889d5b27836fe9b8efcfa:eyJzZWxlY3RlZExpbmVDb3VudCI6MzEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk4YzQ5YTVmODQ4Zjc0ZjQ2MDA5N2RkNzg4ZjRjZTg1NDFlZDQ2YTc1YjBhN2QzNTBhYTlkZmJiZGE3MWI5NWYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjUzNjk0YjQ0NjE1MTE2NTE2ZGFhOGVkYjQ5YjMwMTA4Njk4MGJhMTJiNDIxODZmYjkwOWVhYzNkOGM0OTlhYTIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImE3ZDc2ZjhiZDIzYWI2MmQ4MzU0ZTIxMzRkNjc4MTE1MDBkNzJhOTY1MjQ3NTg0OTViNWFiMzM5MTIzMzdmNTAifQ" + } + ] + }, + { + "id": "claim_7df6e89b1faa440d82708e13e2c7915f", + "statement": "Embedded sessions connect by shelling the system ssh (or mosh): build_ssh_argv/build_mosh_argv turn a resolved host into argv honoring port, identity file, certificate, ProxyJump and ForwardAgent, safe_ssh_target rewrites a leading-dash target to an ssh:// form OpenSSH refuses, and StrictHostKeyChecking=accept-new is injected when a secret is staged or a mosh session runs.", + "evidence": [ + { + "resource": "repo://src/ssh/host.rs#L119-L141", + "version": "repo-lines-v1:sha256:76bf39b0607d1908aa8eb52c6bb20eca84602c45915e4b1cbf8c9dd4b7fbf723:eyJzZWxlY3RlZExpbmVDb3VudCI6MjMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA0Y2E2Y2E4NjM3ODFmNzQ2ODZiNTY5OWY5N2Y5NDU5OTNmMjMzMmVjNWU0MDIyZGI0MjRkMzA0ZTI5MWEyZTciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImM0MjkxYzkyNjNmN2U0YzUwNDRlNDI3MjMxZmQxOTE4OTRiZTU2NjkyZTM0ZjAzMTEzYjc5YjhjNjFhYzRjOTYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjM2MWE1ZGY5MDg0ODRiYmQxMzhlMmYyZDA4OTYzYTgzMmIzZDFmZmRlM2FlYjMwN2Y4ZGNmYWI0ZDc3N2FhM2YifQ" + }, + { + "resource": "repo://src/ssh/host.rs#L30-L107", + "version": "repo-lines-v1:sha256:0d9441a943f0962320bd8da6b4c8dffe8ed2e142bdfb23c7bb0d40b1f6895e6c:eyJzZWxlY3RlZExpbmVDb3VudCI6NzgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjMyNGU0MDMyYjRlZWI5Nzc4NWQwY2E1ZWUxOWYxZDkxOWM3YzJkOTQyMmM3MzQ3YzdmMzBhYTQzZTE0ZjFhOGIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImM1ODc1ZDMzNmI5NmVlMGNkZjdlNmJmYjg4YmU0NDRhYWQzY2MwMGZkZjg1Yjg3MmZiYjRkNTA2NTAyZTMyZWEifQ" + } + ] + }, + { + "id": "claim_bdb1a838258b4985b050beb8a9ccba1f", + "statement": "Host option resolution delegates to the real `ssh -G` run against the SSHUB_SSH_CONFIG / SSH_LAUNCHER_SSH_CONFIG / ~/.ssh/config file, returning hostname/user/port/proxyjump/identityfile/forwardagent/remotecommand; alias listing follows Include directives with recursion depth capped at 16.", + "evidence": [ + { + "resource": "repo://src/ssh/resolver.rs#L45-L96", + "version": "repo-lines-v1:sha256:85c0ab274185abce165863a553afc068c1419aeaea7885009f145cb4b7ea2b89:eyJzZWxlY3RlZExpbmVDb3VudCI6NTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFiYzhiNDUzZjJiZDMyMzQ4NjIwZjc4NzhjYjNiYjlkMGJjNmNiOTcyNTFlN2IzMWU5M2IzNjk1NzFiYzg4YTkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImNlYWI0MDc2ZDRmODFmMmU5MDRlMTAyNGU4MTIzZjBkMzQ4ZmNkNWFlZTNhYzQ0Zjc5ODE5ZmE3OGRkNTY5ZjkifQ" + } + ] + }, + { + "id": "claim_809f83860ea14f8ebd3421be3b7a8866", + "statement": "ssh-keygen serves three integration roles: per-line fingerprinting for the known-hosts manager (ssh-keygen -l -f - via stdin), guarded deletion (ssh-keygen -R), and key/passphrase probing plus key generation and public-key extraction (ssh-keygen -y) — with every passphrase-carrying invocation staging the secret through a KeygenAskpass helper (0600 secret file plus 0700 script exported as SSH_ASKPASS) instead of argv, and probing failing open (None) when ssh-keygen is missing.", + "evidence": [ + { + "resource": "repo://src/known_hosts.rs#L118-L141", + "version": "repo-lines-v1:sha256:86b70538b274f144b0133c3ea7d7912b6cf3b5d128a475507238e14a9ccaaf2d:eyJzZWxlY3RlZExpbmVDb3VudCI6MjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRkYjliYzZhZjhmMThmM2Q0MGYxMDEwNTBkYzgyYmM4MGY3MDAxZDA5Yzg5ODNlOWUwYTNkMWU5NDgzY2IyN2EiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjI4MzExZWQ5ODg2MjJkOGRjM2M0M2I2ODgwOTUwMDhhZTNlYTBmYWY1YmQ3ZjI0ODZlN2QzZWZmY2I3YjBiNDQifQ" + }, + { + "resource": "repo://src/ssh/keyfile.rs#L79-L104", + "version": "repo-lines-v1:sha256:bf3b439f2522b9202926673074ec54df5d3b8d078e80d8e28108778ba403964a:eyJzZWxlY3RlZExpbmVDb3VudCI6MjYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImUwYjA2NDVhNzEyYzI0ZDZjN2ZjZjNkZTY2ZGViM2ZjZjZhYzQxYThmZTQyNzliZDhmYTc4NzU2OGMzNWVjYjYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjkyMzE4YjM0Njk3Njk4NDcxMzA3ZDA2ZGY0ODg4MmM0M2JlYWQ3OTdiNGMyZjNiYzlmY2U2ZTY0NjhhNGY1MjMifQ" + } + ] + }, + { + "id": "claim_2f8b25de36684c68b1bcc10ea96374f7", + "statement": "The SFTP transport verifies host keys trust-on-first-use against ~/.ssh/known_hosts: an unknown key is appended — manually, because libssh2's write_file rewrites the whole file and would drop unparsable lines like @cert-authority — while a changed key is a hard MITM error ('remote host key changed - possible MITM').", + "evidence": [ + { + "resource": "repo://src/sftp/transport.rs#L123-L184", + "version": "repo-lines-v1:sha256:abf4506da33658d7c58bd0b7ad92bb47e49cc01ed6db71788d11cc5f4645658e:eyJzZWxlY3RlZExpbmVDb3VudCI6NjIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImI5YTc0MTg5NjhhMDk5MzNjMDk3N2ZkNmI3ODYyYTA1ZGQ5MzU3YmE1NWYwMDEzZjJhYzQ0NTE1MTExOGNjMzQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjIwMWYxNjQ0YzVlOGI3MGJhZGFkNTI1Y2QyZTI5Mzc1NjYxYzVkM2ZkMDljZjFiZDgyMDk2ODAzZTk5NDg5YmUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjMyNjg4MjdlMmM3MGU4MDc4YTYzZDkwNWU0ZTVmNWZhZDNjMDczOTBmZTU4ZWFjOGQ2ZTA0OGIwMTQ5N2VkZDQifQ" + } + ] + }, + { + "id": "claim_716152f814f1471c8b6955e6af2d5340", + "statement": "Third-party host imports come from Termius backups (L00t.csv plus ssh_keys/, with passwords/passphrases re-stored via store_credential_verified write-verification), PuTTY saved sessions (Windows regedit /e .reg exports or Unix ~/.putty/sessions dirs; only Protocol ssh or absent imported), and mRemoteNG confCons.xml (SSH-protocol connections; encrypted passwords not decrypted) — the latter two creating hosts only via the shared HostImportReport counters.", + "evidence": [ + { + "resource": "repo://src/import/mod.rs#L5-L20", + "version": "repo-lines-v1:sha256:e41761c56047ae1f3f537c7826181c2d2c0c36b78359a9d293efbd1c58aa07b9:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQyZThkYzNjZTI4ZjgzZjJhMzUxNDNmZDc0ZmUxZDMzZTU0ODBkMWVlMjdiMjliOTczMmY0NWU1NjFhNDBjZGUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjY1ZjhjZGNkNjM1NmQ3ZDJiMjc5MzVhZWYzNTU1NDAxMDg4MDAzMmJkMWE2NDljNjRjMmY2NmQyNWNhNDVjYTQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ" + }, + { + "resource": "repo://src/import/mremoteng.rs#L1-L6", + "version": "repo-lines-v1:sha256:ad891a2ef7fd5cbc59b1f5d204c9ed187d8c79586fbc909644e7d1bdc9f8533e:eyJzZWxlY3RlZExpbmVDb3VudCI6NiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZWVmZDcyYzMzY2RmNzRlMzRiYjJmNGJjMTdmMGZiYTM4NWYxOWNiNzc1NTYxMzM0NGRkY2MxODFiMjFmNzY1MyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMzkwN2U0OTA4YjkyNTU0ZGQ5NDcxOGI2NDRkZDcxYjdkNDI1MzNlNWZkYTZiMjQxMDFiNGVjYjMyOWE2NmQ4NiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjAsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZTNiMGM0NDI5OGZjMWMxNDlhZmJmNGM4OTk2ZmI5MjQyN2FlNDFlNDY0OWI5MzRjYTQ5NTk5MWI3ODUyYjg1NSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiN2ZjOTliOWVhMWQ2NjZmZDIwOTVkMGMxYWJlYzY4ZGFjN2RmNmFiZjA4OTAzYjE4YWY0NjRkMzliYWRjZTcwMCJ9" + }, + { + "resource": "repo://src/import/putty.rs#L1-L19", + "version": "repo-lines-v1:sha256:9ba51c60fc501a5bf1ade0f433bf706ecc8747decbbd6f8666ae4296e6f3403c:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxOTlmYWZlZWViYWQ0NmExNTNkZmM4OGQ4YjNlZDNjNTYxMjZiY2FiM2I4MDZhYjc3MDRjNzYxNGJmYjcwYmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImUzMTA3ZDgzNGNkMDg4MGE2NTBiY2E3ZTIyYjIzYTc1NmRiYTBmMWU0ZGNhMzE1YmNjNGM1MGRkODlkOTNiNzMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImZlOWVjODA5YjAwNDRhMTE2OTNjZmNjZDkyOTI5MTI3NDFlMTMzZjQyNDI4N2Q0ODI4MDM4OGRkMTdiOWQyNDAifQ" + }, + { + "resource": "repo://src/import/termius_csv.rs#L357-L377", + "version": "repo-lines-v1:sha256:db0145194d38b0bea35b07e2ec8fa86b7623ca39e09b22488204d1a6e0b417d3:eyJzZWxlY3RlZExpbmVDb3VudCI6MjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA3OTZmNjdlYzY2OGViOTViYjc4NzBjZGI1NjgyMzY0NTk3Mjc2MjUyZDJjYTg3MmI0YzBkMmU2MDNmYjhiNmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQ4NjcyYTJiZTE4MDY1NDJmZmE4MDJiMmNiNGFjYjEyNjVhMTU5ZmVjMTQ2YjBlMjk3NzYwYjRmMzg1NWU5NjkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJjMzk2MTFiNWQ3MzBhMDFkNmFkNzc4ODk4Nzc1NjcwNjEzNDc2ODlhZjk0NDI2ZDIwNGVjZThhZjllMTI3NWIifQ" + } + ] + } + ], + "verification": { + "by": "openwiki/0.5.1", + "at": "2026-09-11T12:46:43.817Z" + } +} diff --git a/openwiki/.claims/operations/build-release.json b/openwiki/.claims/operations/build-release.json new file mode 100644 index 00000000..5285045c --- /dev/null +++ b/openwiki/.claims/operations/build-release.json @@ -0,0 +1,274 @@ +{ + "schemaVersion": 1, + "pageVersion": "sha256:4abf5e74cce6bcf4b6a219080e3c7ec8eaf1618aaf51cd6cc3eb1fac2b4a629f", + "claims": [ + { + "id": "claim_5f6a1b4a3c1d468eb512254eee5a286a", + "statement": "The `vendored` feature (ssh2/vendored-openssl, OpenSSL compiled from source) is a Cargo default feature so `cargo install sshub` and the release tarballs build with no system OpenSSL present; local development and tests opt out with `--no-default-features` (saving ~120 MB of target/ per profile and most of a cold build's wall clock), and nothing that ships — `just build`, CI, or the release workflow — may pass that flag. `just test` runs all four test targets with `--no-default-features`.", + "evidence": [ + { + "resource": "repo://AGENTS.md#L43-L48", + "version": "repo-lines-v1:sha256:cf23bf2b45f78f643283de91f7c5e651b7ac7cbba151306e75ac774e56bf756d:eyJzZWxlY3RlZExpbmVDb3VudCI6NiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMDYzNDA5ZTI0ZWYyZWM0OTkzNjJiYWIyYzYzNTRhMjljYWRmNDExNDNmMzJiZGUzZjQ3ZjdiMTYxNmRjYTdlMSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNmQwMDhlODYyNGEyYTg4MTc2ZGJlNmUwZDg3ZTNkMDFjMDg1YmQ3NjFiZTRkZGQwYjk2NzFiZjhkNWU0OTYzMiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYWExMThhODQ3YjdkM2ViODc0OGMzMWFjNzNkNGRhYjE4YWJmNjUwMWY1N2Q4ZTcxYzgxMmM3MTY5YzQ5OTZhNyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNjkwM2JjYzQzOGI1MWQ1N2M1NDg5ODZiZTdjN2NlZjEyYjBiZTlhMjhjYTI0MjE2YzEyMDNhN2M3MWE1OGRlMiJ9" + }, + { + "resource": "repo://Cargo.toml#L38-L46", + "version": "repo-lines-v1:sha256:e226c47e919c89c2b1523f4130bcb22c44b745e9cae4e63d4bbf80ff164ef87b:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYjZmNmMxYzczZTQ2ODViNmY1NTc5MmFlNTM5MjU1M2ZmNzlkM2NjMDY3YjBiNzRlMDY4Njk4ZjZiMmM3NmI4MiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYzdlNzE4NDM4M2MzMDA1MDc5MDczMmY1ODkzMmVlMjdhYTRjMDNlMmU1OTgwZTEzMzYyM2I1NDM0YzY1YjAzZSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMDM5Njg4ODI0ZGRlNDdjZDQ4YjVjNjVkYTM5YzE4MTU4YmMyZDcyZDJjMDYzYzJjZWFmNjE5MjllZDQ0NjE5NyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNzI2MjZjMmFmYzJkZWVmZTc2ODRhMDg1NDc2MTgzMjRlZGQ2NWU5NDE2MzI2MDA5ZmFlOTU4ODRkYWQyZDRjYyJ9" + }, + { + "resource": "repo://Justfile#L6-L11", + "version": "repo-lines-v1:sha256:a2f03dc0feb9cedf17859d45f3827eccb39d7fe9cc3b9b8872a8c548627717f4:eyJzZWxlY3RlZExpbmVDb3VudCI6NiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNjhlZGM2NWYzZjNlYzJiMmQ0MGM3Yjk4YmIyZTQwZDljZWUzNWMzMGUwMDhhZGY5MjBmMzNlNDU1OTA4MDNjZCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZTc5NTEzYzNlYjJhNDUzODA4YzRjN2UwNmJmYjFhNTI0N2IzYzM2MmM3NGUyNjgzNjgxODVkMjNhN2JhM2YxZSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiOTJhZjEyNGMxZTc5MmJkMDJmZWIzMzk4OTRkNzcyZGNmMDQ4MmMzZGJlODIzMjlkMDg5YmQxMjIxNDRjNjhhYSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNjZhMzczMTg5YzkzYTFlZDU3MTA1YTY0NDM1ZjA2OTQ5MmI1MGE3Mjc5NTA2ODQxZmEwY2MwMWU0NjE5NmQzOSJ9" + } + ] + }, + { + "id": "claim_29920455885742dd9afb36a791de4fd2", + "statement": "[profile.release] sets lto=\"thin\", codegen-units=1, and strip=\"symbols\": release builds are slow but the binary is optimized across crate boundaries (this is what the tarballs, cargo install, and npm ship), and strip=symbols removes the symbol table from an already debug-info-free release build, so RUST_BACKTRACE output is empty in any shipped binary — a readable panic backtrace requires a debug build. The dev profile uses debug=\"line-tables-only\" (full DWARF was ~40% of target/), and panic stays on unwind so a worker-thread panic kills only that thread (TUI stays alive) and cleanup Drops run, including AskpassSecret's removal of the host's askpass password file.", + "evidence": [ + { + "resource": "repo://Cargo.toml#L99-L123", + "version": "repo-lines-v1:sha256:00d182bb39395869d0694e47f69be3dcef1de0efe6306c007b441ce52473bb8a:eyJzZWxlY3RlZExpbmVDb3VudCI6MjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRjMGVhYTliMWU2ZGExNTk4Y2M1MjQ1ZjRkMDMzZWNhZTZkMTdlYTNiMDdmOGIxM2RhYjllNzAyMjVjMDRlZjEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ0YzllYzI2OTEwOTZkNGY5YjhlNzExYjc3NzJjYjc4YjY0MWU5MThkZTY5NDIzYzkyMGUyMjg5MWMwNzEyZDMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUwNDZjZmRjOTFlODhhMzgwMTVkYmFlZGRiNmIyODgwMDVmYzMxNmRiMDFhNWM0MmM3M2JjZTU3OTdkOTQyMDciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ" + } + ] + }, + { + "id": "claim_0cc26b3e465847e284700d15b3d1be29", + "statement": "`just bump [version]` implements odometer versioning (Z rolls 0–9 carrying into Y; Y rolls 0–99 carrying into X, e.g. 0.9.9+patch→1.0.0, 0.99.9+patch→1.0.0) by sed-editing the version in Cargo.toml and the sshub entry in Cargo.lock — no cargo invocation, keeping the pre-commit hook fast and offline; `set` validates the X.Y.Z shape.", + "evidence": [ + { + "resource": "repo://CLAUDE.md#L31-L43", + "version": "repo-lines-v1:sha256:4c219e398ebee7fe5d17baa03edbcd229c53d19f4a658a1a2b8f0b6ee4986e99:eyJzZWxlY3RlZExpbmVDb3VudCI6MTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI0NTYyMjNmMTJiMTI0YjAyYTQ0Y2U4MzlhMjIyNmU4ZTY4ZTdmY2VhYzQzNGQxMTkyY2FmMTRmOWZkNGQwOWMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk1NWY5NDg0NDM5MjQ5NWQ5MmMzOGViOGZhOWMzYzc1OWM4YzY5OGU1OGJjOWE4MmE1MGUyYmRkZWI2NjYxNmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImY3MGQ1NjQ1YzU1Y2YyMDY0YjkzMmQyZWZiMTY5YjE0ZWU4Y2RmZjNjZTAxNjBmNDFhNGZiZTkwMGYxMjk3MmQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImE4MjRlYWI1ZWUxYmExNTBkZDA3OTI4YWFlMmY4MGM3MjdmOTA1M2M0OTFlMzk0NGJhY2E4NjVhOWI2ZmJhZTUifQ" + }, + { + "resource": "repo://Justfile#L104-L131", + "version": "repo-lines-v1:sha256:8d515b666cb7130b897b965b7402c8849d983f1b3dd4d4c4aac97f66a1feb810:eyJzZWxlY3RlZExpbmVDb3VudCI6MjgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjgzNzc3MDE3Zjg2ZjhlNjdhOThlNTViYmFmZDEzNDFhZWEzOWFmZTU0ODIyOGRmNmVmY2E1NDkzNDk4NjliNTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjEyOWQ1MDA5OTY5OTZjMWQ1NDBhMTg2MWUzYjg1ZjI5ZDlhZGNlYWNmNjFjZTA2NTRiZmIzMmExMmRlNWY4NjgiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjFlMWFmYjFkZWQ2YzAyODY3ZTNlZGFiOWM1ZjlkOTE2NTQ4MjRkODg0MDEwOGE4NTk5MTk4YzAxOTQ1NzBlYzgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQ1NGJmMWQ3MTg5Nzc1ZjEyZmEwYzgxMDNhYWVlMzkyZWNmMTZiYTcyMDMyMWViNDA3NzgzYmUyODM4ODU2YjkifQ" + } + ] + }, + { + "id": "claim_1662c1c7ec2e45948075ada492fce072", + "statement": "The tracked .githooks/pre-commit hook auto-bumps the patch version on every commit to development (runs `just bump patch`, re-stages Cargo.toml + Cargo.lock) and is enabled per checkout via `just setup-hooks` (git config core.hooksPath .githooks); it exits 0 on any other branch, while a merge/rebase/cherry-pick is in progress, or when `just` is not installed, so it can never block a contributor; it also checks the branch-name shape via scripts/check-branch-name.sh (the substantive commit-type check runs in CI).", + "evidence": [ + { + "resource": "repo://.githooks/pre-commit#L1-L40", + "version": "repo-lines-v1:sha256:abf55da8c99272a5628307ae5227b60cf3051dabe05c49dbc849a2af1eaebd4b:eyJzZWxlY3RlZExpbmVDb3VudCI6NDAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFkOTVmYzA0YTgwYzk1MmY0OWNlNDE4ODYyN2M1M2IwZmJlOGM0NDA0MWI5NTJkNTkyYWNkMWRlOTk4NjE0NjYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI4OGRjNjBiYjYyODhhOGZlZDJkNDUwZWY1NTQ0MmU4MWI4YjY2NWQ2MDZjMjc0ZDc3ZTRjYzYwNjk4MGU4YTUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ" + }, + { + "resource": "repo://CONTRIBUTING.md#L65-L68", + "version": "repo-lines-v1:sha256:151c0cb6f896e8855a27b4cdb1422863e0f8ac40fe82ae468f032cf9af945403:eyJzZWxlY3RlZExpbmVDb3VudCI6NCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNDRlNjM2YThhZGY3NjA1ODQ5MTAzOTc2ZjVlYTdiOWRmYzA4ZmY0Y2FkOTAwOWZlOTVkMGM3MDhhNjlkYWNlNSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiOTlhNjJiYmZiNjk4MGIxMzYzNTY2N2UxMjk3ZTc3YTY3YWFlY2JiMWUwMDVjOWJhOGIzZDIxYTk0NjY1NzNiOCIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZjAzM2MxNTJlODRkMzg2YjA2ZWIxYTcxNmU3MzAwOTIwYTVhYjNlMjhmZTk3ZjkzMzQ4MDJiODExODU2Njk3NCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiOWQ2YzY5OGYzYTcxZmUwNWRkMDc4ZWNkNmQxZDJiZjhhMWU3NzJjYTIxMzRkZDYyM2FmYjkxYjA0M2E3MzEwZCJ9" + }, + { + "resource": "repo://Justfile#L133-L137", + "version": "repo-lines-v1:sha256:c8111a9b9a0ac5167eced8837e59848d79c336a5d00960f1f04f18adc5b42c6f:eyJzZWxlY3RlZExpbmVDb3VudCI6NSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiM2UxMjY0OWY2MzUwYjYwNzBhMjg3ZWNlZjFkZGE4MjY2MzBhMTdmZmFmMDdiN2MzZWQ2ZTI5MDljN2ZkOWJmNSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNmJlNjM1MzdmMmYxMjNjNGYwMTBhZmU5NTRjNjdhMTA5ZjA2N2QwMzQ1MTgwMzJkYTdiNzIyMmRmOTQ2YjZmZiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMmY3ZTZiYjIwOWZmYzUxODc3NWQwYTU1YTYxM2I0MTg1MWU4Y2JjM2I4ZTZjZWMyNzFiZTc2ZWJjYzE2Njg4OSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiYWFhMTNjMzI2ZTBlMTA1OGM0YzU5NDQ4YzYxNjU0M2U0MTQ3NjJjNGQyM2U5ZGMwMjdmOGVjNTE3MzhkZWI3YiJ9" + } + ] + }, + { + "id": "claim_5353ca776f6e4d23bc1a89d104ee7ecc", + "statement": "`just release ` (run from a clean development; an explicit kind is required because a bare `just release` used to silently mean minor) settles the release version ON development (so the dev odometer continues from the released X.Y.Z instead of going stale and colliding with existing tags), rolls the CHANGELOG ([Unreleased] → [X.Y.Z] - with a fresh empty [Unreleased] on top), commits as `chore: prep release vX.Y.Z` with --no-verify so the patch-bump hook cannot re-bump, refuses an already-tagged vX.Y.Z (restoring the version files), then --no-ff merges development into main, pushes an annotated vX.Y.Z tag, and fast-forwards development to the release merge.", + "evidence": [ + { + "resource": "repo://CLAUDE.md#L45-L53", + "version": "repo-lines-v1:sha256:485f04bdbb7ae9cb4d12943ac850c8ea217973fdb44ea3f1779e2d93016cdd5d:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZDUyZmI1OWE5OGJiZDFiYzA0N2ZmZGVhMDlhNGJmY2JlOTQ3MjA3NWU0M2RjNDEwYjQxZDJjYTRjNTM1YWYxOSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYjZiMzQ3NzhhOTA2OWE5ZTEzMWFkMjI0ZmJiYjAwMmEzMTMxNTllNjE4YzNkMTM0ZWQ2NDNkYzQxMDg5Y2E3ZSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZWYzZjA3Y2U5NmRiZWJlZWViOWJiYTEzNDYxYWIxYTEwMzc0NzlhNmIwNzNkYWRiY2E5MDI0YjJhMTc4ZGE3OCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiN2RhYWE3ZThlYzM2ZmFhOWYzMWQ1NTRlY2UwNmJlOTc1MjFmYzBmYTRhZDkyMzQwMzNhZGY5MzNhYmM4NmJiZSJ9" + }, + { + "resource": "repo://Justfile#L261-L346", + "version": "repo-lines-v1:sha256:98fcfa92d1b93a8aab41ade962922cc5222a8db9b6e84ce3566f2082fc41738a:eyJzZWxlY3RlZExpbmVDb3VudCI6ODYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM0YmFlZmY0NmMwZTU4YzhiYmNkZjBkODdjOTJkMGMzMWYzM2YyYTk5OWU5YzdiOGVlMGQ3OWJlZjJhZTUzZDMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRjMzA0ZjdiYmY0NzA1MjQxMTA1MTI2MTM1ZTA0Yjg3ZWRhNGRmZGJlYzNlNDBlMzM3ZjE3NDcxMjM3YmY3MWQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImZjZDdlNmFhOGUxODVhYjU3NzVlYWQ4MjU3MjFmNGU3ZDc2MzFkY2MzY2U0MzMwYjE5MzM5ZGFjYmExZjgwMTQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjAyOTRjOTk3NGU4MjhjY2ExOWYwYjBhZjY5NWMxNWZiNTZiNDQ4MTViNGJmNWNiYjdjNmQwZTliMzhlZWEyODEifQ" + } + ] + }, + { + "id": "claim_337b827417ac48b78c1348c6ade21aed", + "statement": "Release kind semantics: `minor` bumps Y and resets Z to land vX.Y.0; `patch` ships development's current X.Y.Z unchanged (the hotfix fast path, so main is NOT always X.Y.0); an explicit X.Y.Z sets that exact version with no bump dance. Because patch publishes whatever development currently holds, land only the fix first if development carries unreleased work.", + "evidence": [ + { + "resource": "repo://CLAUDE.md#L39-L39", + "version": "repo-lines-v1:sha256:ac691ec22c65a2ab6530538d498163abc046ee91a1106fb195d7006cc6e92ff6:eyJzZWxlY3RlZExpbmVDb3VudCI6MSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYWM2OTFlYzIyYzY1YTJhYjY1MzA1MzhkNDk4MTYzYWJjMDQ2ZWU5MWExMTA2ZmIxOTVkNzAwNmNjNmU5MmZmNiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYWM2OTFlYzIyYzY1YTJhYjY1MzA1MzhkNDk4MTYzYWJjMDQ2ZWU5MWExMTA2ZmIxOTVkNzAwNmNjNmU5MmZmNiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYWUxMDExMzU4NGQ1YjRiNmY1NWRmZThmZjk4MzJiYTI4Y2YzNWVjMDcxNGRjNThmNTdiMTNjZWQ0MjEyOGI4MyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNTYxNzJjNmQwODA4YTMxYWQ0NTFhM2JiODVhZDBmNDFhNTgzYWI0NDAwZjYxYzU4ODkxYjkwZjIzNTU4MWQ3NCJ9" + }, + { + "resource": "repo://CLAUDE.md#L47-L49", + "version": "repo-lines-v1:sha256:5c266057b17791a08dc93336340d7c863760cea844ea45a74b1ebf7c65bee0c7:eyJzZWxlY3RlZExpbmVDb3VudCI6MywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMWJlYWU3YjBlMDgyYzgxYmVjNjAwMTVhMTM3MjlmYzY3ZTNjY2FiZWFhMWE2ZmUyYjAzNWQ3NzNmMDAxYTE1MiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYTZiYTMwNzllOGUxMGU4ZjQzNmE1NGY5NDdjMzU0MmE1MWUyZGU2ZTRhZTZhY2Y5MmY3MjQ1YzU5ZTk4YmJhNSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYTgyNGVhYjVlZTFiYTE1MGRkMDc5MjhhYWUyZjgwYzcyN2Y5MDUzYzQ5MWUzOTQ0YmFjYTg2NWE5YjZmYmFlNSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNWNmNzEzODcxMmJmM2JmOWFjMDM0MDk4MDJhNTQzNzEyYzc0YjcxZmQ4ODQwZmY3NjhiNTQ1OTg1ZGFjZDRiMyJ9" + }, + { + "resource": "repo://Justfile#L270-L306", + "version": "repo-lines-v1:sha256:f492779abb42ff3a630c3711a7b8bcf722a3556d81d89bf6ce577fd09844da1d:eyJzZWxlY3RlZExpbmVDb3VudCI6MzcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImYwYjkzZWZmYWQ5NTgzYjVhYzhjYWY1NTQ3ZTAxMWZkNzc5NzQ5ZGIyNmFkNzNlMDU1N2RhOTc5MDYxOThlMGIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQyZDVkZTI3MWU1ODNjNjVkNzcxYzk3MTZkYjJjOWY2YjdjNmY5NjNhMDU0MTQwOGRmMjJjMTQ1Y2Q5ODA2ZTciLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQ0MzE3ZmFhOTlmOTJjMjljMzQ2YzUxNzAzZDk3ZmIwMDY4NTA3ZDNiYjFkOWY4MDlhMWQyNWFhNGY1NjY5YjIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjkzYzljOTkyMGQ4ZDJhNjdmOTg3NGMwYzdiZTRmMTdhM2UxNDdhNzA2MWNkMTY1MDQ4NmY0YzNiOGVjNTc4YTYifQ" + } + ] + }, + { + "id": "claim_6d2a750ecd8e4617a15e938e03e3084e", + "statement": "Because development is fast-forwarded to main after every release/sync, main is always an ancestor of development and the next release merges cleanly; main's first-parent line shows one commit per release, reverting a whole release is `git revert -m 1 `, reverting one feature is reverting its squashed dev commit, and re-landing history after a merge revert requires a revert of the revert. Pushing to protected main relies on the owner's admin bypass.", + "evidence": [ + { + "resource": "repo://CLAUDE.md#L51-L51", + "version": "repo-lines-v1:sha256:e35b3d1c7425aff458fca7bd810ebf12184df4102a1d2e07e33ef076edd811c1:eyJzZWxlY3RlZExpbmVDb3VudCI6MSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZTM1YjNkMWM3NDI1YWZmNDU4ZmNhN2JkODEwZWJmMTIxODRkZjQxMDJhMWQyZTA3ZTMzZWYwNzZlZGQ4MTFjMSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZTM1YjNkMWM3NDI1YWZmNDU4ZmNhN2JkODEwZWJmMTIxODRkZjQxMDJhMWQyZTA3ZTMzZWYwNzZlZGQ4MTFjMSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNDFmODUyNTZiNTVlNDU5ZTBkMTRmYWQ5MTdjZTUzYTAyYjEyMWMxZWEyODU1ZjQ4NzdhN2EzMjBmMmQ4YTY2MyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNDJmYzE2MDg1NTQ0MWNiNmYwNTExYTM1NDgzMzRjMzdiN2EwN2VjNGMwZjY4ODMzNWQ0N2U3YjYwODU4MzhhZiJ9" + }, + { + "resource": "repo://Justfile#L331-L346", + "version": "repo-lines-v1:sha256:e031c82ecd7d392b181f2728fedbc29bafb3718f1efc4d0846e8c5b2ee32e4d5:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjhhNDNjYmNkZWJhNGYxY2QxMDFkMDA4OTdhMmI1MmJiYTg3ODcyYzQ5MTlmZTVjNzRhOGM0MjAxNzUyM2U4MDgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRjMzA0ZjdiYmY0NzA1MjQxMTA1MTI2MTM1ZTA0Yjg3ZWRhNGRmZGJlYzNlNDBlMzM3ZjE3NDcxMjM3YmY3MWQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjMwZjk2MjBjYzcyM2RlNTJkYjEzNGViMDE4NDc3Y2JiZjQ3MDVlNjYzYTY3ZTA5NDMwMzliMTMzZTc2NmIxNTgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjAyOTRjOTk3NGU4MjhjY2ExOWYwYjBhZjY5NWMxNWZiNTZiNDQ4MTViNGJmNWNiYjdjNmQwZTliMzhlZWEyODEifQ" + } + ] + }, + { + "id": "claim_6d8932fa2dd348fc962f082cda62150c", + "statement": "`just sync` merges development into main between releases WITHOUT cutting a release: no version bump, no CHANGELOG roll, and no tag, so the release workflow (which triggers on v* tags) is not fired; it creates a single `chore: sync development into main` first-parent merge and then fast-forwards development back so the next release still merges cleanly.", + "evidence": [ + { + "resource": "repo://Justfile#L348-L378", + "version": "repo-lines-v1:sha256:c6f125ee383a94be5d694d5fe95b0c27dc434a624201d37b0bb298a883843a72:eyJzZWxlY3RlZExpbmVDb3VudCI6MzEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ2ZDMzMTczNDc4MWRiNDNmZWMyMDAyZjExYTAwMDhlMjBlYjIzMDAwOGU2YjcwZjQ3MTY0YTQyZmQ3MjZhNWEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjdiNTk3YjEzODVkMTRhNGNjYmE0ODI0MzE0YzQ5ZDMzYjEyYmNhMDdjZjNhZGQ2Mjc4N2YxM2NmNzljMTExNmEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjY5ODE1OTk3Mjk5NDI0YjIyYWUyYzFmNTg1YmVjY2I5NTk3MWZiOTYzMzhhOGRiZDNmMTIyMTE2Y2Y0Y2I2ZmQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjVmN2JhZGZkMGNhN2VhMjI3YmZjMDc1OTk3NzQzNzJhYjYzZjU2NDJmNDNlODM1MmI1YmZmMjUyN2JkZGE2ZGQifQ" + } + ] + }, + { + "id": "claim_8dcb394b35094c859b2591db0d28cd2a", + "statement": "Hotfixes that must exclude work already on development cannot use `just release` (it refuses to run outside development and ships everything there); the CLAUDE.md flow branches fix/release-X.Y.Z from origin/main, cherry-picks the squashed per-PR commits, scrubs the excluded feature out of what ships (its CHANGELOG entries stay behind and other entries mentioning it must lose the mention — grep src/, man/, README.md, completions, help text), verifies the release tree (a --release build proving the feature is absent), opens a PR into main for CI only without using the merge button (main's first-parent must read one `chore: release vX.Y.Z` per release), merges and tags locally, and finally merges main back into development right away — skipping that merge lets the next development→main merge quietly revert the fix.", + "evidence": [ + { + "resource": "repo://CLAUDE.md#L55-L68", + "version": "repo-lines-v1:sha256:779973be482795afb1e93de48f1f3e54a0fdf584e8a2dce350f0457fc0aa6bfe:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImMzZGJlNGUxZWE0YWVhYTZhMjcxNzRkYzRmMGQzODUyMWQ4YmNmMjE1MzM3OGQzOGQzYzA1ZGJiNjQ1MzdmYWYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjlkNDY4ZmRiMTcwZjBhNDY1YzVjMmEwYjg4ODY3YmMwZjAzOGM3MWY4MzYxNTdjODM5ZDljNThmNGI1ZDVmMzkiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQyZmMxNjA4NTU0NDFjYjZmMDUxMWEzNTQ4MzM0YzM3YjdhMDdlYzRjMGY2ODgzMzVkNDdlN2I2MDg1ODM4YWYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjFhNDkxNzExZTBhOTA1NWMzNDFiYWVlNDMwMjhlYjcxNzAwZWFlM2JjNWUyNzdmODI3NzNiM2RjMWJiYTI1MmYifQ" + } + ] + }, + { + "id": "claim_7935c8d5f6604bb9a6f2067bcc8ccfab", + "statement": "Multi-agent build infrastructure: every checkout/worktree points build output at one shared sibling `../.cargo-target` via an untracked .cargo/config.toml with an ABSOLUTE build.target-dir written by `just setup-shared-target` (run once per checkout; migrates an existing ./target and any target symlink). There is deliberately no `target` symlink because `cargo clean` deletes it, after which cargo silently builds into a private ./target and the shared dir becomes one copy per agent. `just worktree-add [branch]` creates ../.worktrees/ on feature/ (default) sharing that target, resolving the main checkout via `git rev-parse --git-common-dir` because `--show-toplevel` is wrong when run from a worktree; `just worktree-rm` force-removes it (branch kept unless delete-branch) and sweeps stale artifacts; `just sweep [size=4GB]` prunes the shared dir with cargo-sweep --maxsize, which must be invoked on the project path (cargo-sweep resolves target-dir itself). Never run two cargo builds or two `just test` at once against the shared target — fingerprint races; serialize builds.", + "evidence": [ + { + "resource": "repo://AGENTS.md#L14-L51", + "version": "repo-lines-v1:sha256:79ed8a87d6912d3e22086831ee265c44f92dfba18d5620867a810e1fb21456dc:eyJzZWxlY3RlZExpbmVDb3VudCI6MzgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTJmMzhmMTQyNDI2MWM1NmQ3YjNkN2M1YzJjY2U1MzkwZDcyZWQ2ODZjOGRiOTllOGQ5MTQzYmQxNTdhNGQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjEzNDdjNmQwZjIwZWQyNjU2MzAyNjE1YzFhMGE5ZTNkMThjM2UwMDY0YzgzZGRlOWI1OTE1OWQyYWNkZDUwYzAiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjY5MzkxMmNkM2MzM2VkZmQyM2QzNzY0ZTE3YWIyOTc0N2IzNzkzNzQyMzZjNjRhNzZiMGE1ZmZlNDhhZDM4MGUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjYwODQ5NWYzMzBlOGRmMjY5YTZkZWNlYjMyZDhhYTk2OTA1MmMwN2IxZWI5MGExYzFjNDA2NTA0NDAxNWFmNjcifQ" + }, + { + "resource": "repo://Justfile#L139-L259", + "version": "repo-lines-v1:sha256:535c3cd5d1d3fb0ebb40e9268ba7fb556acac76ebb73de131bcc40215f95278c:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIxLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiJhOTlmMjdjMmViZDZlYjY1OTA3ZTgwZjNlNmQyMjBiNmFlNDUzZTM1YTQ5MGY0NjhmYjkwOTExMWNiMzQ4NjE1IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI4Njc5ZWJlOTA2MmNhY2EzOTk2MGVjMmY4YzA1Y2YxNzNhNWFlOGUyODQzYmYxM2MyZWY3ZWUyMDFiNTkzOGZhIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJjMTVlYTlmZTE2Nzg0YjM0MDc2NzlkZjdhMzFmYWM4MDEwYzg3YTQ4MzhmMGYxZjk4NTI0NjkxNjhhYjBiZWU4IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiI5MjUxMWY1OTEzNjI4ZDUzNTQ1YmE2NjFkN2M0MjIwZDRjYjM2M2ZhYTdiNWFlZDk3MmNhNjliYzNmZjJlNmIzIn0" + } + ] + }, + { + "id": "claim_6bb18d8f1db147a8ab3bd63adea2f67e", + "statement": "`just npm-build [version]` (npm/build.sh) assembles the npm distribution in npm/dist with NO compilation: it downloads the sshub-*.tar.gz artifacts attached to the vX.Y.Z GitHub release (gh release download; TARBALL_DIR=dir uses local copies, which CI passes), extracts each binary into a per-platform package (sshub-linux-x64, sshub-darwin-arm64, sshub-darwin-x64) with package.json/README rendered from npm/platform/*.in including os/cpu matchers, assembles the sshub-tui wrapper around npm/wrapper/bin/sshub.js (package.json.in pins the three platform packages as optionalDependencies), then verifies on Linux x64 that the extracted binary reports the packaged version and that the shim — driven via a node_modules symlink exactly the way npx would — reaches it and reports the same, and npm-packs each package dry-run.", + "evidence": [ + { + "resource": "repo://Justfile#L43-L53", + "version": "repo-lines-v1:sha256:fecbdec21377214b9f58cdddf3c5bc20748aef9a5ffc17cb60794a4c6df7d72d:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk0MzdmNWMzMjY1YzViMzA2NWNjN2M1NDI4YjNiY2I1ZTNmY2E1MGVjNWIwN2E1NGIxNWQ0ZWQxMjVmNmRmNDgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVhMGIzZjdhYjJiNTBkZTU3Zjg2NmRiZGMyZGEwNTlmNWI4MWIzNTliMTFiMzAwNTc0OGRiMDJkZWE4M2U5YjciLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjYzNTEzMDY1NmJiNTYwMWJkMTNhM2JlNzcyZjc3NmVkMmFiZWE2ODBmY2NkZWNkMGQ0MmVlMDA4NDk1MDgwNDkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjVmZTliOWNiMjZmMjA4NGJjNjI5ZjY3YWUwNjRkNGEyYjc0ZmI2MmMxNDhkMWM4MWY0ODQ5YTJiM2IwMDNiOWMifQ" + }, + { + "resource": "repo://npm/build.sh#L1-L122", + "version": "repo-lines-v1:sha256:14a74d1d73c966a5c1c1eedf8757eae9aeda95b833cc1fbf184feb7723b5e476:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIyLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiIxZDk1ZmMwNGE4MGM5NTJmNDljZTQxODg2MjdjNTNiMGZiZThjNDQwNDFiOTUyZDU5MmFjZDFkZTk5ODYxNDY2IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiJkMTE3ZmEwMDZiYTkyMDg1MDBiMjkzMGNlNjljYmRlNDM2YzY0N2FmYTkxN2NiNzM5NmE5YmM5MTExYTQ2ZGQyIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MCwicHJlY2VkaW5nQ29udGV4dEhhc2giOiJlM2IwYzQ0Mjk4ZmMxYzE0OWFmYmY0Yzg5OTZmYjkyNDI3YWU0MWU0NjQ5YjkzNGNhNDk1OTkxYjc4NTJiODU1IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiJkNWI4NTczMDkwOWJkMmMzY2QzZDhkYzZhNzcxM2M0MjhjMDg2Y2Q1Y2U2YmU3MGJkNTEwY2RkOTc1YTRiNTkxIn0" + } + ] + }, + { + "id": "claim_1a5156fdb0d3462c93ce078de03099c5", + "statement": "`just npm-publish [version]` runs the same script with --publish and publishes the three platform packages FIRST and the `sshub-tui` wrapper LAST, because the wrapper's optionalDependencies must exist by the time it lands — the other order resolves the wrapper to no binary and installs fail for everyone in that window; versions already in the registry are skipped rather than fatal, so re-runs after partial failure are idempotent. The wrapper package is named sshub-tui because npm rejects the bare `sshub` name as too close to the existing ssh2/sshpk packages, while the command it installs is still `sshub`.", + "evidence": [ + { + "resource": "repo://npm/build.sh#L130-L142", + "version": "repo-lines-v1:sha256:64277bd3f128787c5bad379acba7848fe330c9054c9b579a5a98377bd63b38cd:eyJzZWxlY3RlZExpbmVDb3VudCI6MTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjgwMWQ1NjEwZWM5MzgzMzkxMjVkMTM0ZWEwNGU4MDVjMTBkOGFiMzJkMjk1MGIyMjk5NzAzZmEzYWM2ZTg3MDkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjMwODVjZTQxODIxZDVmZjFkYzBmMWM4MDJiMjZhMmI5OGNiNzZhZmMzYmFmYzgwMWYwMjExOGIzOGEyY2E3OWIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQ3MGQxN2Q0OWI4NjEwNzdjYjc3MTQ3Yjc1MTYwODA3OGNjNWRiNzQ1NzI1MTE5MTg4YTZlOWExZDExOGVjNzEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ" + }, + { + "resource": "repo://npm/build.sh#L42-L51", + "version": "repo-lines-v1:sha256:89642d2343e6e7f522e92dd9181c4f90ccc07907a9ad75f7100eea1095ec805a:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU5YmUzMTc5ZjEzMzM5ZTI5ZWM1MjMzNDQ4MThjMWY4NDFlZGFiMDdhMWQwZGE5MjUwODAwZWJjZGQ1NDdkNTQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFmYTQ3ZWIwYmQzNDZlNGNmZDRhMGU1NjAxZTRmMmQwYzM5MWQyM2QzN2RhZGVhOTYzZjc4ZWQ2ZTYyOWUzY2QiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjIwMWQwNWZmNjYxZmM3NzZlNjIyOWRkODMzNzFiMDQ0ZmY5NzAyMDRkNjgyMTAwZDkyZjU0MmMzOTk3ZWFlOGMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImY3YTMwNDE1MjhhNTNlNGFmZDczY2EwZmI1ZTBlMTY0MWRmOWU4ZTdiNzRhZWRhMjI2MDE3NjQyNTNjNzk2NTEifQ" + }, + { + "resource": "repo://npm/wrapper/README.md#L11-L12", + "version": "repo-lines-v1:sha256:cf7400ee328cc5c2a30a63e46b3d837280b720ba6066b21f7d42e514e837b8fa:eyJzZWxlY3RlZExpbmVDb3VudCI6MiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiOGU2NzU1YmEyZWU2YWYwZTkzYmU1M2JlZWEzMjlhNTM2ZmRiZDQzZTcyMjBmZmFhNTQxYzI2YjI2MzhlZmQ1ZSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiM2ZjZGFhNzY4ZTNmYWE2MGY1OWUwYzgyM2Y2NjY1MmViMTUyNDJjZDU5ZDhhMGJiOTZhZDFlZGRhZWQzYmM0MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYTRiY2E4OTlkYzVlZTMyMDMyNGVkZGUyZGE2YWM2NWEzZWJkYjA3ODYxM2FhMzU1NzA5OTY1YTE4NTAwNWU2YyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiM2E3YzMyODc1NjA4OTRmOTA3YmY0MmNmODY1MmE0N2I0YmUyMWM4Y2QyYTU3YTUwNzAwNWUyMDA3YTU2MDI1OSJ9" + }, + { + "resource": "repo://npm/wrapper/package.json.in", + "version": "repo-file-v1:sha256:ec500f2c93e6d1bf1e1262a288675bd5902bead93f6c701c05fdd0aeaf42af2f" + } + ] + }, + { + "id": "claim_8515a9e3e72143108ad2b0c0f86c9e9d", + "statement": "The npm shim (sshub-tui's bin/sshub.js) never downloads anything: it maps process.platform-arch to the one platform optional-dependency that installed (die() with build-from-source guidance on unsupported platforms or a missing package), require.resolves its bin/sshub, repairs a dropped executable bit, and spawns the binary with stdio:'inherit' (hands the real tty to the TUI; same process group so Ctrl+C reaches the child) plus SSHUB_INSTALL_CHANNEL=npm — the env marker the version badge reads, because the prebuilt binary is byte-identical to the release-tarball one and path heuristics cannot tell the install channels apart; it propagates the child's signal and exit status.", + "evidence": [ + { + "resource": "repo://npm/wrapper/bin/sshub.js", + "version": "repo-file-v1:sha256:727f424ad3f10e25e84310f0fb9ac6faaabfd326d321d61a5f6518e38dcf096a" + } + ] + }, + { + "id": "claim_934f88389f2d4f68a3321506deca98f6", + "statement": "A pushed vX.Y.Z tag is the single trigger for every distribution channel: release.yml's build matrix (linux-x64, macOS arm64, macOS x64) runs `cargo build --release` on default features, smoke-tests each binary it ships (--dry-run, or codesign+verify for the Intel Mac binary an arm64 runner cannot execute), attaches the tarballs to a GitHub release whose body is CHANGELOG.md, publishes to crates.io with CARGO_REGISTRY_TOKEN (fails loudly on re-run), and its npm job runs npm/build.sh \"\" --publish with the same artifacts as TARBALL_DIR — the version comes from the tag so a mismatch fails in CI instead of reaching the registry — authenticated by npm trusted publishing (OIDC, short-lived tokens + provenance, needing npm >= 11.5.1 and a per-package trusted-publisher config, else ENEEDAUTH).", + "evidence": [ + { + "resource": "repo://.github/workflows/release.yml", + "version": "repo-file-v1:sha256:0e3815daf92c1dea0a2ca767a5516798181cef9e2e4405a50242e27b3e2d53f2" + }, + { + "resource": "repo://Justfile#L261-L264", + "version": "repo-lines-v1:sha256:25c0011485df9926e6be4b7f04cd33dd74adae71be48649e364b52f5d59cb727:eyJzZWxlY3RlZExpbmVDb3VudCI6NCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMzRiYWVmZjQ2YzBlNThjOGJiY2RmMGQ4N2M5MmQwYzMxZjMzZjJhOTk5ZTljN2I4ZWUwZDc5YmVmMmFlNTNkMyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiODZhOTlmYmNhMzU2YmVlNTljYjNiZjg5OGUzMjgxZjUwMzdmNDIzNWM5Zjg2MDZkZTRjYjNiNzkyOWIxMDU4YSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZmNkN2U2YWE4ZTE4NWFiNTc3NWVhZDgyNTcyMWY0ZTdkNzYzMWRjYzNjZTQzMzBiMTkzMzlkYWNiYTFmODAxNCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiN2M0Yzc5ZDY0YTZhNDE2MGNmMzI2ZjE0NjA3YTQ1MDRmMTZiZDcyMDYwY2VlZmMzOTVmNzg1Y2E5YzQ5OWFiZSJ9" + } + ] + }, + { + "id": "claim_933ebfda2ec042df8c7d1099be916bc8", + "statement": "`just coverage` runs cargo-llvm-cov twice (JSON export + summary) and pipes the JSON into scripts/uncovered-functions.py, regenerating docs/coverage-map.md's tables: the src/ functions NO test executes even once, largest first (the useful number, since a never-entered function has never been observed working). It needs cargo-llvm-cov plus llvm-tools-preview on rustup, or the system llvm-cov/llvm-profdata on a distro toolchain (the recipe sets LLVM_COV/LLVM_PROFDATA itself). The script handles the traps that produced wrong numbers before: counts summed per mangled name across test binaries, line segments (not zero counts) as the arbiter of degenerate entries, and single-line spans discarded as non-functions. There is deliberately no coverage-percentage floor in CI.", + "evidence": [ + { + "resource": "repo://Justfile#L13-L37", + "version": "repo-lines-v1:sha256:c7d743c891442a232eea57ebd323e647a5f4d05f29506250fc64ace250575aa0:eyJzZWxlY3RlZExpbmVDb3VudCI6MjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjlkZWNmMDI0NjBkNTI2ZDE0ZWI5N2Y5NTdjNTRjNmFkMDQyYWQ5ZDYzYzIyNjkwMWY5ZDhjNTczYjRiNTc3ZmQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFlNTMzMDM4Y2RjYTc2YTM1NjFiNjNmNDRkNTM0ZGJkMzBhNmNjZjFiMjZiODEyNGJmMTcxZWU0ZTM5MzJiOTIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjljMDM4MjI5OTZiNmJmMmY1N2ZjYTgyMmU2NmMwZWE0MTllMzIzYjVkYjNmMzM3M2NmMGE4ODY5MmM5OTAyMzQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjNjYjczNjFhNTQxNjk2ZDcyYjJkMzZhMDU1MzQ4NTViNTEyNDVhODc4MzBhMGY5YWFkYjcxNTEyMzJiOGQ0MGQifQ" + }, + { + "resource": "repo://docs/coverage-map.md#L83-L86", + "version": "repo-lines-v1:sha256:f7fa79b81e1352331ca6ba98ec180ee6dea3037a963dad6d64388861c78aae03:eyJzZWxlY3RlZExpbmVDb3VudCI6NCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYTRiOTFjZDcxNGM3NjkwMjZlYjdjMDgzNmQxMDIzNDcwMGQ0ODEzMTk0YjdhNzgzN2MzZDlmYzhkMDY5YzM1MiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNWRmMDFmMWJlMTVkMGM3ZmEyMjNlOTJhZmUyY2EwODZjNDI3YmU3NTYwYWM5YmNhZTc3ZjMyZWJjNjdlMzg0OCIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNGYxN2Q4NzY2MjliY2UwMTAwZTJmZjg5NjczMDFjZTAzMDdhZGIxMTI0MTE3YzE0NWEwMjUwODUyOTY3MWQ2YyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNGFkZWUzYzdjZTZhNWM5YjAzZmY2MjdhODgyMWI0OGZmODI5N2VlOWM0N2E2ZjljZDAxZjU5NGNlNTEwNDJjZiJ9" + }, + { + "resource": "repo://scripts/uncovered-functions.py#L39-L105", + "version": "repo-lines-v1:sha256:d5b0f9d2bf09d0085cec467265e0b07eb6920ecaffd44661d36212c83280759a:eyJzZWxlY3RlZExpbmVDb3VudCI6NjcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImExNThkMzM2MjNkNTk1YmVlMDEyOTA0OTBhNDg0MTdjZTVlMWUxNjIxZmFlZmU5Y2YyZGVmZWY3YmI0ZTBkNjIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjE2MjMzYjNmZmVhMzJhMjNkZjUzMDY1MzQzNDhmMmRlZTc3NmE1YTZmNzRmYjUzMzE3NWRkYzUzOWE4ZmU0NjQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImIyOWI4NTEwNzdlZDkwNzA3YWE5NzAzY2I0NDk2MTdmZDcwNzkxNDg4NTJmNmY4ODgzMWE5ZjljY2JmNjVkZmMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjNkNDEzNDBjYzIzOWExNTljZTliNjAyMmU5ZjYzMjI4MzJlMmVhZDc2Y2FlOWU0NzU0ZDIxMGViOGZiNmQ1ZTgifQ" + } + ] + }, + { + "id": "claim_f98f2516d8d347448fed4691d265048c", + "statement": "The lint gate — `cargo fmt`, `cargo fmt --check`, `cargo clippy --all-targets` — must pass before every push, and CI runs the same (clippy with -D warnings, failing on any warning); CI also runs the test suite on Ubuntu and macOS and a branch-naming job that self-tests scripts/check-branch-name.sh and checks every commit type on the branch (skipping forks).", + "evidence": [ + { + "resource": "repo://.github/workflows/ci.yml#L59-L76", + "version": "repo-lines-v1:sha256:27228789a75b7524b4cb77001f6207b19083ccea8073b8b4a4d186c9ed4469ea:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImY4ZmI3Mjc3ZmRkMjBkMmY4NDY2ZmM3M2I1ZTQyNzA0Nzg5YjA5OTkxNjYyODlkY2IxOTM1YzAwNDg1MTZlZGEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImM2Zjk4NzEzYTBiYTZmNGRiZDVkNmNhYzY3OTAxZmI0NGZmMmMwZGVmNWFkNDgxZTJjMzZmM2M3NmYxOGNhMDAiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImY1ZGVjNjAyZjcxNWMyYjc4YmM3YzA5OGNhZjdjOTgyNWJhNzRmM2RiODc1NmY4YmFhNTQ4NTI5NzZiNzYxYWIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ" + }, + { + "resource": "repo://AGENTS.md#L90-L99", + "version": "repo-lines-v1:sha256:0bb267ddde60754a7d63cae4fdc4655d95ec7249b4a934c28023c10c341ba6ec:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjY1YzkyYjAwMjE0YjFhNDBjMWFmYjQ3NWE2NjFiYTYwYTIzMjU0MGM1ZjJhNDg1Y2ZlNmIxYmZkMjg4NzI2ZGUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjY5MWJhNjc3YTRmMDBiNzAwMDY5N2I1MjYxNThlN2YzNzE5MDNlNzlhNjE3OTdiMWQyODIzZmFkZWNiYjQ5MDgiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjA0ZTYwMDJkMGEwNDQxMmI1MTU1OWZlZDI4NGFjOWU0ZWM3NDU5MDc1NjQ0YjM1YjkwNTliZThkM2UwNzljZDciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijg5MWIxMWY3NDk2YjFkZWFmOTFhMDUwMDQzMDAzN2Y3OTQ0NzUxOTJjMTNhYjUzMDZjMmQ1NDlkZTA0OWU5MjcifQ" + } + ] + }, + { + "id": "claim_f08df0416e5a4d3f81fa6a5fcb2c7868", + "statement": "`just install` (depends on build and install-completions) installs the release binary to ~/.local/bin plus the man page, icon, and a .desktop launcher entry with terminal detection kitty→ghostty→alacritty→foot→xterm; `install-completions` writes bash/fish completions into auto-loaded dirs and appends a marked, idempotent sourcing block to ~/.zshrc (compinit ensured before compdef); `just uninstall` removes all of it, including stripping the zsh block.", + "evidence": [ + { + "resource": "repo://Justfile#L380-L416", + "version": "repo-lines-v1:sha256:458295ad7d67ac828797c0401837595be4d8ab713f767cee343ca2180aa8d652:eyJzZWxlY3RlZExpbmVDb3VudCI6MzcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjlhZTAwMGI5MDQ5ZjQyZjE4ZDhjY2UyOTc3NTMzYTI4MmE3ZDFiNzk0OTQ3Y2I2OThmMDJiOWYxYzUzNTgxYjAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI1MzA1YTZiMjMxOTllMGEzNzQ0MDUwNzI0MTVkNDc4Mzc1ZGM2Yzg0YmI2OWVlNzVmMGUyNGQwMTcwMzU0YzIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE0NTA0MjRjMTFlYjZjYjAyMTgxNDg5NjY5YzlhYjUyN2U5ZDJmMmMyZDE5NzVmNzk3YjI0YmIzYjNmZjUxNTAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ" + }, + { + "resource": "repo://Justfile#L69-L101", + "version": "repo-lines-v1:sha256:1ffc9dd30dc83410b2f85caa3bfdc15227b6171975d581488455c9bfe4e01b30:eyJzZWxlY3RlZExpbmVDb3VudCI6MzMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRlOWZhNDhkNWUwYzc2M2Y1YzA0NDM0OGMwZWEzZjc4ZWUxYmYzMTVlNDZiMWE1NjlkMGIzMmI0OWJhNjE0MmMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijg0ZTc2MDQyNmE4ZTNhODYwZGQxNTBlNmMxMTc4YmFhNmJmMTgyNGM1N2ZlODI3M2IwMmI0YTVjZTQ1M2FhNzEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImJlNWU0ODJmNzkzYTBlZWZkZmRlMjc1NDZmOGU2YzBkZmQwOGExMjU1Nzg2YzAzODVmZjI2OGFmMGIyMDA0N2IiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJiMmYyYWY3ZjE0YjdjNGYyOGFlY2I0YTczNDQyN2EzMTc5MTQ5NDVjMTJkYzAyOGVkNzc1MjRlODY1MjQyNDkifQ" + } + ] + }, + { + "id": "claim_ff0e3467838d4921ad943ae735725443", + "statement": "Branch model: feature/fix/docs/chore/poc branches are cut from development (never main, which receives releases only and must never be a PR target), PRs target development, release merges go development→main, and merged branches are deleted (GitHub auto-delete). Names are / keyed to what the work IS (feature/=feat, fix/=fix, docs/=docs, chore/=chore/ci/build/refactor/perf/style/test, poc/=anything), enforced by scripts/check-branch-name.sh in the pre-commit hook (shape only) and in CI (substantive commit-type check; forks skipped); never bump Cargo.toml on a non-development branch — versioning is automated.", + "evidence": [ + { + "resource": "repo://AGENTS.md#L64-L88", + "version": "repo-lines-v1:sha256:5722c993adb6a0a30ef6b9c971b3ebdd49f2ac70333d91a2429a876ef2157783:eyJzZWxlY3RlZExpbmVDb3VudCI6MjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImIyN2NkZjZjNDBmYTg0MGQyZjRiY2ZlOGQzYzg2MWUxZmMzMmY0MDgxN2U5NzNiY2ZmYmE5MGUyMGY0ZmY0ZTEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI0YWE3MGVmZDA3MzU5ODY3YWE0MzA1OTM1ZWU3YTNiMmU5OGIxZWNmODA0ZWRhMzJlZDE5Yjk1Mjc4MTM3ZWMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQ4YmFlMTU1YTAzYzFiMTg1ZGI2Y2EyYjAxM2E1YjljZGVmM2M5NmRhYmY0MTdiMDNiZmFmYTUzOTcyMzM4ODAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJjYjU2NjdhMmQ0YjIyMmJkYjc5MjU1MGQzNjJlN2UxZTU1NzdmMGU1Yjg1NGEwODg2NjM3NzNhYTkyZDUzZTAifQ" + }, + { + "resource": "repo://CONTRIBUTING.md#L12-L25", + "version": "repo-lines-v1:sha256:3462363540a08f76c90190f1d106fa034bfb603bef74d7b67bd3012e294c8645:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFiMjFjMDVlZDM5NTlkMTRmYzY0NTkxNTFiZTI3MGNjNTA2M2ZkY2M4NDBlY2IwN2QyZDRmMWQwYTA4ODA2ODkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRlMjc4MDZmNTZkYWY5ZDhjMjg3YTA4ZGE3MjQ4MzAwOTU0MTA2ZjdiOTNhYWRmODBhZmEwMDhjZjA5ZWJhNDUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImEzYTM1Mjc1ZWRjZDJkODM3MTIzYjZlMjlmNzExNTM3NThjOTliZjY1NTQwZGI4MTg3YTg5YTNjNzc2MmYwODAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjBiOWI2MGUyMTZkMzVjZDNiNjU4ZDVhNWQwNjcyNTNkNWUxMzBkZDVjYmJkNWJiMjc0NDExMDZhM2E0YTI3OTUifQ" + }, + { + "resource": "repo://Justfile#L283-L295", + "version": "repo-lines-v1:sha256:a32a95cc3b91b10aa2d2206b7520f8dd2e318135f1ac914f89d406cf02fafbc3:eyJzZWxlY3RlZExpbmVDb3VudCI6MTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjJjMTJmN2YzMzdjOGIzZjQxNjNmYTA0NTNmMzg3ODM0MTc3ZWQzMDJjNjA2ZmMzYThiYmRiOWI4MmFkMTZkNGMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjEzYjcyNDE2MTM0YjgzNTk5NjZmZTcxZTFlNzE2YTA3MWYyMDU5YzFiMGJmZWI3ZDdhMWM5ZGE4NzdiMGM0OWUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjRlMzYxOGM4NzQwOGNkNmRlZDNjYzZjZTAxMDNjOGEwNjliZjMxMzdhNDVkYzEyYTIwNDAzYWU5YmU5NmZhMzYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImY3ZTBhYmEzYzNkNzNkNTExZDcwNDQ2OTY4ZTYzMmQ1Njk1YzM5NjhjZTEzMDI5MjI1MjI4NjZjYTUxN2I3YjcifQ" + } + ] + } + ], + "verification": { + "by": "openwiki/0.5.1", + "at": "2026-09-11T12:46:43.817Z" + } +} diff --git a/openwiki/.claims/operations/ci-cd.json b/openwiki/.claims/operations/ci-cd.json new file mode 100644 index 00000000..3c419e57 --- /dev/null +++ b/openwiki/.claims/operations/ci-cd.json @@ -0,0 +1,210 @@ +{ + "schemaVersion": 1, + "pageVersion": "sha256:d9d09cd2a0cb35a2271b5a0b777266d80b53eec0823d9c84bca7a0118d3aceb8", + "claims": [ + { + "id": "claim_b61c2c510afc42e493c38ef4e944530e", + "statement": "The ci.yml `branch` job enforces branch naming only for same-repo pull requests (forks are skipped), first running `scripts/check-branch-name.sh --self-test` to check the checker, then deriving the conventional-commit type of every commit in `BASE_SHA..HEAD_SHA` and passing those types to the script so the prefix is checked against the branch's actual commits, not just its shape.", + "evidence": [ + { + "resource": "repo://.github/workflows/ci.yml#L15-L38", + "version": "repo-lines-v1:sha256:0bb5ee7794537460055a9e184899985268ca2f1bd8de03823827e66ac782d81f:eyJzZWxlY3RlZExpbmVDb3VudCI6MjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRhNmVhYmE5ODY0MWZmMTNjY2E0MTZkMzhmODIzZTZjNTI4MWNkNzJiN2FiMmVmMDJmNjdjNGFiYWNmZWMyODgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRhNTEwN2YxYTdhZTIxYjExN2Y0ZjcwMTVmZjcxYmRiMWJiOTgxNjBmNTQ3YjljOTAxNjE3NWNjMGExZDQ0MmYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjEwMmM3MTllZjZjYjBiM2ZkMDQ3OTIxMjQyYmUyMGY1NDNkYjcwOWVkNTc4ZGYwMzBjZmQ0NGZjODVmNGU2NWQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjY4M2QwMzkwOTY1OWQzMzk3ODhkNTg4OWM0MDYxNDE5NDAwZDAxZGFlZDBmZmNjMmNhMzg0ZTg1MWVhYjc3MzIifQ" + }, + { + "resource": "repo://scripts/check-branch-name.sh#L10-L17", + "version": "repo-lines-v1:sha256:a273f42b8531986b680f45fe1d1ed61917cb198f687f29f3561c29a773826095:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNzUyNDA0YTU1NmUyYjM5ZTgxZGY5NzM5MDhmZDdhYWE4NTMyNDljZDk0ZWUyMjI2ZWIzZGI1Zjg1MjZjYzgyOSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMWZmMTAzZGQzY2I1Nzc2MjRhMTJhNzY4MTlhYjUyNmIxYzYzYTk2OGJhZWVlODE0YmFiYWJjNTZjYzNhNTIwNiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNzU4YjIzNDEwM2IxYmRhY2RkNTAwOGM3NTIxMzk3NDc1ZjlkZTE1Mjc0Njg1ZDc4ZDk0ZTc5Yzk3YjhmNDg2YyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZjliYTY3NzZmYzJiNDhmNTNmYzI5ZTI5MWZiMmQ0ODIzY2E0MGQxMjZhNzk4MjIxNDgwZGI0MjJhMDY1MDRhMCJ9" + } + ] + }, + { + "id": "claim_c94a2e3add2247dba2892cd5a37a887c", + "statement": "`scripts/check-branch-name.sh` exempts names owned by git or a workflow (main, master, development, HEAD, openwiki/update), requires `/` shape with a known prefix, and — only when commit types are supplied — requires at least one commit type that justifies the prefix: feature←feat, fix←fix, docs←docs, chore←chore/ci/build/refactor/perf/style/test, poc←anything; supporting commits of other types alongside are fine.", + "evidence": [ + { + "resource": "repo://scripts/check-branch-name.sh#L21-L33", + "version": "repo-lines-v1:sha256:de87d49733f44249aeaf86aa90481c7575b3e4ed64259e8d6103f2f7d993aae8:eyJzZWxlY3RlZExpbmVDb3VudCI6MTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ0ODZmNDFhOWIwMTI5MGIzZDA0NDUyY2FlNjhmMGU4YzJkMmU4MTA2NzIyNzliNDYyNmYwZWE3OGQ4MTg0MGIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YWIyNTNjNWY5NjAxN2VhNmU1ODhkYjUxMWMyM2Q2MTAyNDhhZTdhNDExZDI1MzJiYWNiMjIwMGNiYTU3MjkiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImY5YmE2Nzc2ZmMyYjQ4ZjUzZmMyOWUyOTFmYjJkNDgyM2NhNDBkMTI2YTc5ODIyMTQ4MGRiNDIyYTA2NTA0YTAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImFlZDMxMjhiNGQwNDhmNDk3YjhiYjlmZGEwMzhiNTFiMmVlOWVjYjk5MjAyODgxNGQ1ODBmMzQwMzhiMjQzYTIifQ" + }, + { + "resource": "repo://scripts/check-branch-name.sh#L43-L77", + "version": "repo-lines-v1:sha256:c2ef5e63713108e5a51a63b978841ae9919b3ea79ec965e73214c4266dbf8bf3:eyJzZWxlY3RlZExpbmVDb3VudCI6MzUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjIyNjBjOTQ5ZmI5NjM5ZmQxNmU0ZWU4ODRjYmY2Yzg4NWI3MzI2MTg3MjY5YjliNGZmODdlY2UwMzc5ZjE1ODUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImM3NWVmYTBmMDQxYTc3MmMxMThiZWM0NDllNWUzNzExMTc5NTMwMGNkZmZhNTA3OTU1YzE4YTNlMjk4ZDU1YjAiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImNiODliYzg3NjY4MWY5MzllMjQ1NTZmN2FjZGI0MmRiOTA0YmRiNWYyNjE3NzMzNWY0NDA5MjQ2ZWU1NjgxOTAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI2YmUwNzUxY2Q3ZjI1YTcyNWQwN2Q0MzQxZGY1ZDU4NTcwNTViNDBjM2MzODkzYWNkNDI4YzBmYTlhZTIyZTEifQ" + } + ] + }, + { + "id": "claim_b94f9f4e5e594c70980fc80cb8753857", + "statement": "Branch-name checking is deliberately split in two modes: the tracked pre-commit hook checks shape only (the commit justifying the prefix may not be written yet), while CI sees the whole branch and enforces substance — the check that catches a pure bug fix on a `feature/` branch (#95), which turned the AGENTS.md prose into a check — and the script's self-test case table runs in the same CI job so the checker itself cannot silently rot.", + "evidence": [ + { + "resource": "repo://.githooks/pre-commit#L13-L23", + "version": "repo-lines-v1:sha256:2ac9a634f1ed3102c941d8ca382cbd83f3275e85f91355942dc18f683149f6b0:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkzOTgwY2Y2ZGI4ZTgyNjE1MGNiYjYyZWZjOWI3NGVlY2M4Yjc3MGQwYjQ5OTc4ZTlhZmZjOWE0MTU3MjY1NjkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM3NjlkMjM3Y2Q0MjBiOWQzOGI5ODFhMGE0ZjY3NzAxOTBhNGE4M2RjYTFmZTU2YzRmNWJhMWUyY2JjMGVmNzYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjAwNDMzODZkNGQ3ODJlM2Q4ZWYwYzM1NTdlZWUyYWQ5ZjJhMjY4NTdiM2IzNTc1MWEzODc3MDllMmJmY2Q2NzkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjFmZTA2OTY2ODJkOWFiYjA3ZTA0MmY1MWZhZTNlMmIyZjgwMjM3NzU5N2MxOWNjZDgzZGVhODZhODQzMTNiNjcifQ" + }, + { + "resource": "repo://AGENTS.md#L81-L88", + "version": "repo-lines-v1:sha256:5c99a9ff58efec30c1202e12987e5cbc19aaa84969563c1495348e828f533a43:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYjkxYjNhYTdmZjMzNjU2M2RmMWJmOTA0MGEzMjRmNWRhZDAxYThmYjMyZTliNTUwYmQ3MWZiMWNiNjJlY2I0NCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMjRhYTcwZWZkMDczNTk4NjdhYTQzMDU5MzVlZTdhM2IyZTk4YjFlY2Y4MDRlZGEzMmVkMTliOTUyNzgxMzdlYyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMTUwNDc2MTcwN2E2ZjBmNDkwYjNmOGRlZjdhNDcwNDQwOGRiYmFlNTljZDAwMWE3MWM2MTU0YjU2MGJlNTgyNiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMmNiNTY2N2EyZDRiMjIyYmRiNzkyNTUwZDM2MmU3ZTFlNTU3N2YwZTViODU0YTA4ODY2Mzc3M2FhOTJkNTNlMCJ9" + }, + { + "resource": "repo://scripts/check-branch-name.sh#L80-L118", + "version": "repo-lines-v1:sha256:e29575195249695545b6079ec6c9da227f5923025b4975f984600cdee51e4d32:eyJzZWxlY3RlZExpbmVDb3VudCI6MzksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImExMmEwMTM5YjBmYWNiYjcyZmM3OGFkNjBmZTc0YjAwNmRlNmRjZjRiZmI5Y2NhMTFmODQ3Zjg1YTU3MDg4ODAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE1NjFkNjRkNWM3MGNkNWVkNDczYTQ1MDA3Mzk4YmU2ZjY5MmM0NDlhOTEwMjRmOTYzODhhZTUyMmRjM2JiMGEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjExYmU2OTBjMzczOGQ0YWUwNjBjY2M2M2Y4YjI5NzY0YzZmMjY2MzM5YTYwNWNiOGEyZTg5ODI2NzY4MDE4MDEifQ" + } + ] + }, + { + "id": "claim_5095272a86584bf2a432968d4bd56ccf", + "statement": "The ci.yml `test` job is a fail-fast:false matrix over ubuntu-latest and macos-latest that installs libdbus-1-dev + pkg-config on Linux for the keyring Secret Service backend, then runs `cargo build --all-targets` and `cargo test` with default features — vendored OpenSSL stays on, since anything that ships must build without system OpenSSL (local `just test` opts out with --no-default-features for speed; CI does not).", + "evidence": [ + { + "resource": "repo://.github/workflows/ci.yml#L40-L57", + "version": "repo-lines-v1:sha256:9d2e17d5ee63044cacb7cbe0b750c47ce85abd3fb7667eb73a2cc13d74155b65:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImIyMDA1Nzc3NzZjNjY4YjcwMWQ3MTY1YTk3MmNiYWJiNmVkZWQxMjhiYjg1ZmE3MGMzMjYwYjU4ZTBlOWIwNDQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijg5MGM5NzRiNTVkODQzZTIyZTQyYzA2MThlZmQzZTliZWYxYmE0MDUyYjk4NWY1ZWE1YmQzZmE2YTU0ZDllMWYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQyYmU0NDZlNzY1YjMzNjUyN2I2NzQxMmIzYmFjZjQ1N2FmMmZmYzYyNTEwNDI2ZDkwNDNmNWE5ODk5ZTkxNzQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE2ZDc4YzdhMmYwODUxNDIxZmZlMDczZjhmYzZmNWZkZmNkZjVjZjQ0ZDZjYTU1YWVhOGU5MWNkNDg5ZTE2OTMifQ" + }, + { + "resource": "repo://Cargo.toml#L38-L46", + "version": "repo-lines-v1:sha256:e226c47e919c89c2b1523f4130bcb22c44b745e9cae4e63d4bbf80ff164ef87b:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYjZmNmMxYzczZTQ2ODViNmY1NTc5MmFlNTM5MjU1M2ZmNzlkM2NjMDY3YjBiNzRlMDY4Njk4ZjZiMmM3NmI4MiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYzdlNzE4NDM4M2MzMDA1MDc5MDczMmY1ODkzMmVlMjdhYTRjMDNlMmU1OTgwZTEzMzYyM2I1NDM0YzY1YjAzZSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMDM5Njg4ODI0ZGRlNDdjZDQ4YjVjNjVkYTM5YzE4MTU4YmMyZDcyZDJjMDYzYzJjZWFmNjE5MjllZDQ0NjE5NyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNzI2MjZjMmFmYzJkZWVmZTc2ODRhMDg1NDc2MTgzMjRlZGQ2NWU5NDE2MzI2MDA5ZmFlOTU4ODRkYWQyZDRjYyJ9" + } + ] + }, + { + "id": "claim_cd3a19e9ebce407ca36ec3975fd7e095", + "statement": "The ci.yml `lint` job runs `cargo fmt --check` and `cargo clippy --all-targets -- -D warnings`: the tree has been warning-free since #50, so any clippy warning now fails the build (without the flag the pile regrew — 42 warnings on the `e2e` target masked a real one in new code); the job installs libdbus unconditionally because --all-targets compiles everything.", + "evidence": [ + { + "resource": "repo://.github/workflows/ci.yml#L59-L76", + "version": "repo-lines-v1:sha256:27228789a75b7524b4cb77001f6207b19083ccea8073b8b4a4d186c9ed4469ea:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImY4ZmI3Mjc3ZmRkMjBkMmY4NDY2ZmM3M2I1ZTQyNzA0Nzg5YjA5OTkxNjYyODlkY2IxOTM1YzAwNDg1MTZlZGEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImM2Zjk4NzEzYTBiYTZmNGRiZDVkNmNhYzY3OTAxZmI0NGZmMmMwZGVmNWFkNDgxZTJjMzZmM2M3NmYxOGNhMDAiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImY1ZGVjNjAyZjcxNWMyYjc4YmM3YzA5OGNhZjdjOTgyNWJhNzRmM2RiODc1NmY4YmFhNTQ4NTI5NzZiNzYxYWIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ" + } + ] + }, + { + "id": "claim_a525d68137984976936761fce4e0f84e", + "statement": "release.yml triggers on a `v*` tag push (created by `just release`, which merges development → main and tags with --follow-tags) with top-level contents:write, and its release, npm, and publish jobs all `needs: build`, so nothing is published to any channel until all three target binaries have been built.", + "evidence": [ + { + "resource": "repo://.github/workflows/release.yml#L3-L13", + "version": "repo-lines-v1:sha256:72842f58117a2006dbbd8c38e528b5f3a4d65eb38fb5a1053d760ed0b261d5fa:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU5NmRjNDgxNDlmNWE4M2QxZWQ2ZGJiMzliYmM3ZTJiOTAyYWEzZDM0ZDZiZDVhZTRiMjkxYjY4ZGYwY2E3OWEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU5ZDgwMTU0ZWQ0MmZjNWM3NzliMTMzMTU3OGI1OGI4OWM4MDFlYWNlNzRmMjRjZDQ4ZWI0YTIzNTc5NDBhOTUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjoyLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjVmOWNhZTQxNDE4YTVmOGY0YTFhODI5ZmRkZDAyYmY3NzQ4MmUwYjkwMmQzYzBiNGZmYzExYTg3NGZlNGQxZTgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE1N2ZkZWEyMDkxMGRmY2U2MTY0ZGRmYTRlNDU5ODA2M2EzZTlkYzlhMWEzODgzOGFlYmMxMzA5ZTlhOTkwZWQifQ" + }, + { + "resource": "repo://Justfile#L337-L346", + "version": "repo-lines-v1:sha256:e0c6d25384bb913fecf4a46becbcd5da6c6131c7a32f156e1082d2e90b86b76e:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjIxMjBlZjZjYjJiMWQ0YjNlMzA5OTA1MzgwODJjODJjNWY0MWM3OTAyZjQyY2ExMTI2ODE0YzU1ZWE2MDUyNzQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRjMzA0ZjdiYmY0NzA1MjQxMTA1MTI2MTM1ZTA0Yjg3ZWRhNGRmZGJlYzNlNDBlMzM3ZjE3NDcxMjM3YmY3MWQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE1N2QzN2VmZGMyYmY1ZDY4NjQ4YTgxMjRlMzFmNjZkNDllNmUxMzFlMmEwN2IzODUyODQ2YTQyMWY4ZWNmM2QiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjAyOTRjOTk3NGU4MjhjY2ExOWYwYjBhZjY5NWMxNWZiNTZiNDQ4MTViNGJmNWNiYjdjNmQwZTliMzhlZWEyODEifQ" + } + ] + }, + { + "id": "claim_02a575391ceb492f927b63c9acc0aa95", + "statement": "The release build matrix covers x86_64-unknown-linux-gnu (ubuntu) plus aarch64-apple-darwin and x86_64-apple-darwin (macos), and every leg validates its binary before packaging: the two natively-runnable targets run `sshub --dry-run` (which exits without opening the TUI) as a liveness check against strip-induced breakage, while the Intel Mac binary — which an arm64 runner cannot execute — is checked structurally via a `file` Mach-O type check plus `codesign --sign -` and `codesign --verify --strict`, because cross-compiled x86_64 Mach-Os carry no linker ad-hoc signature (verify alone only reported 'code object is not signed at all', as happened for v0.11.0).", + "evidence": [ + { + "resource": "repo://.github/workflows/release.yml#L14-L63", + "version": "repo-lines-v1:sha256:9aa2a09ff5a75e87a079c26d70ab7e530cd44eb0019a3f35128713619c2fb96a:eyJzZWxlY3RlZExpbmVDb3VudCI6NTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjdlZTNjNjBkYjI1M2NiY2VhNzI0YWFkZDkxOGViOTQ2NjRmN2M2N2U3ODRmNzk2Zjc4NGNmMTAyMjA3ZDI2YmUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNhNzg5N2YxZTFjZGI1YWY2MGVjYjA4Mjg4MzRhMmNmZTYzYzM4MGM5YzE5NzA5NDA3YjA3NmY5ZGYzMDE5NDYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjFjNWRjZGMwN2Y3OTNkMjQ5N2VlOWRmM2U2YzI4OTI4MGRhOThjY2I4NzFlMGI3NDc0NjgxNzdkMjRkMWVkYzUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImFlNTBhMGVhOWZhZWM0NWMwODk4OTBhZWQzMzYyMjZjM2RlZDU0NGRhYmVjOWFiZjlhMTk4Mzc5ZGJjM2Q4MmUifQ" + }, + { + "resource": "repo://src/main.rs#L74-L76", + "version": "repo-lines-v1:sha256:0eb36cb45b4b19a3743be0e647f09bb183285cb30a57c642a74a10398d8e46b8:eyJzZWxlY3RlZExpbmVDb3VudCI6MywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYzE5ZWRiYzM2YzgyYjllMmJjOTNiZTViOWRmYTlmMDQ2ZjU1MDJjMzQxNDQyN2ZiODE2Y2ViMmQxZGM2NjA0NiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiOTk5YmRlZmVlYWNhNjM0MGUwMjkwZGM4NmU4ODljODlkNWUxODlhYTViYTQ0YjU4NWIzYjcwNzM0ZjRhNjJkOCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZWFlYTkyYzlkN2VhZDQ4Njc0ODUxNTM2ZjE4NGJjMGVlOTA5YmY4MGUxM2IwYjQwOTg3MDcwYmE1NGFiMzIxYiJ9" + } + ] + }, + { + "id": "claim_2c0570529fa94374bd5a03dc7c7e8579", + "statement": "The release job merges all build artifacts (download-artifact with merge-multiple) and creates the GitHub release via softprops/action-gh-release@v2, attaching the sshub-*.tar.gz files with CHANGELOG.md as the release body and generate_release_notes disabled, so the changelog section `just release` rolled is the release text.", + "evidence": [ + { + "resource": "repo://.github/workflows/release.yml#L73-L85", + "version": "repo-lines-v1:sha256:892be980d4aec704bba44e734a75c5ae31205e55eec5b16793ef1a1e01722b7a:eyJzZWxlY3RlZExpbmVDb3VudCI6MTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjYzMmUyNTRlMDAzZGRiMzY4ZjUxYWM3NTg0MTc3YjdhY2RiYmZjYTQzYTcyMGJmZWIyYTY1MWYxZTFjNDBjNDUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjdkYTc2YWFhZjdmNjZkMDJhMjQxOWFkYTJhZTlmODI5MDYzNjNmMWIwMDI0YmM5Mjc4ZDk1NDI4NzA0YTEyN2UiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjFkNGNjMjkxNmRmMTRkOTJhM2ZjOTUzNDdhY2NjZGM4ZjIzNDJlMDUwZTlhMDA3OTZiZTYyNDdmNGQyNjMwZjYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjM0OTM0NTBhMWQ5ZGI0NDVhZWIzZjU4ZGY4ZDQ4YWNiZDZhZDAwMThkZjMyNzdlM2E1MTFhZDQwMjg4Y2UyNTYifQ" + }, + { + "resource": "repo://CHANGELOG.md#L5-L7", + "version": "repo-lines-v1:sha256:fa4b7983e9f75d8415ffe5ae4a5f6e06d8737dcaedd71681be378cc7e57d8a23:eyJzZWxlY3RlZExpbmVDb3VudCI6MywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMGE4YWJjMTI4NDNjNDg0Y2ZkZGIzMjU1ODYyNWE4ZmU1YTI1NzU4MTQ2ZTc0OWMwMWI4MWMxMzUyNjRmNGY1MyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNWI3Zjk0YWE5YzhjNWI0ODQwYmYwNDczZmI5MWUxN2MwNzlmN2YxMjdkNjQxOTdlNmI0MDY0MTcwMjA0MGJiMiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYjdkMzgyNzBmMTMyMjQ4OTdkZGFlNWQxNDhhZmU1ODIxZGNhY2NiMmQwMDEzOWIzOWE3M2Q4MTRhNTVkNDZkMCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNjA4NzdhN2MzODZhOGUwNDhhYTI5MmUxZGE0ODc3OTAxN2U4ZmQyNWI3ZjI3NGI5OTE5ODM1ZmRhOWM2NjA3ZSJ9" + } + ] + }, + { + "id": "claim_87c7785bde764a7c85a4f4fcee941b1f", + "statement": "The npm job publishes with OIDC trusted publishing (id-token: write, no long-lived NPM_TOKEN; npm ≥ 11.5.1 is installed because Node bundles an older one) and runs `npm/build.sh \"${GITHUB_REF_NAME#v}\" --publish`, taking the version from the tag rather than Cargo.toml so a tag/version mismatch fails in CI; npm/build.sh repacks the release tarballs byte-for-byte, publishes the three platform packages first and the sshub-tui wrapper last (the wrapper's pinned optionalDependencies must exist when it lands), skips versions already in the registry (idempotent re-runs), and each of the four packages needs a trusted-publisher entry on npmjs.com or the step fails with ENEEDAUTH.", + "evidence": [ + { + "resource": "repo://.github/workflows/release.yml#L87-L126", + "version": "repo-lines-v1:sha256:46d93b89478e40e7af1b36bfd6e606c31bd19bd8f4e209e91c8e05d8750a4db0:eyJzZWxlY3RlZExpbmVDb3VudCI6NDAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImYzMzE2Y2RjOTZhYzA2ZTA0YjFhYjI4Nzk3NWQ4ZGJhM2JhNmExZThhZWIwZTYwMzdkOGJjNWUzMTFkNmJhMTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjUxN2U2NTMzNThiOGRmMzEwM2Y0ZjM4NzBhNDA0ZThjNjMxZGQ0OTRmOWI0YjljN2RlYmRiYjgwM2YzZGEyZTUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjhiNzIyZWRkODE3ODM4ZDc4MjNiMzRhODU0OGYwMjFhNjc5MWY1MDcxNjk0OGM1ZjYxYmU2YmFhZTBhOWY2M2EiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImFmNzcyMTBkYmQyNmJmZjI4YzUwZWFiODAxMzNkMjM4OGJkYTk3ZmFiNDBhMTZhMWY3NzYwODc1MDQwMTAyMzEifQ" + }, + { + "resource": "repo://npm/build.sh#L120-L141", + "version": "repo-lines-v1:sha256:7e255d4d18285b29e6c709e0ebd66c0ee02abf36b427b59de0aa56fac2a40871:eyJzZWxlY3RlZExpbmVDb3VudCI6MjIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjIyOTVkZGIzMzk0ZjdiZTI4YWQ1ODYyNGVjNWI0ZDA2ZThjODEwNDIwZTJkZjVhNzQzNDQyY2RjYmZjZTI1MGYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQxMTdmYTAwNmJhOTIwODUwMGIyOTMwY2U2OWNiZGU0MzZjNjQ3YWZhOTE3Y2I3Mzk2YTliYzkxMTFhNDZkZDIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjAwOWUzN2NiNWUyN2FmNmEzZjRmM2MxODgwMTQ4NzBjM2FiMGMzYjU5OGFkODE4MDQ0ZDc5NDgyOTZhNzhiNDIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjMwODVjZTQxODIxZDVmZjFkYzBmMWM4MDJiMjZhMmI5OGNiNzZhZmMzYmFmYzgwMWYwMjExOGIzOGEyY2E3OWIifQ" + }, + { + "resource": "repo://npm/build.sh#L43-L51", + "version": "repo-lines-v1:sha256:fecee81aba8cdc30041f514b7d1ae9d6ef58d6b66977bcd2e8a77a9bbeb8b4ee:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZjVlMjgxNTRiYWYyMDkyMTBmOTI4NTI5OGJkODg5ZTA4OTUxNWQwMmQ2NTZiNTZjZGIwZWRiODBjY2U2MzAwZiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYWZhNDdlYjBiZDM0NmU0Y2ZkNGEwZTU2MDFlNGYyZDBjMzkxZDIzZDM3ZGFkZWE5NjNmNzhlZDZlNjI5ZTNjZCIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZjAxNzgzNDhiMmY1NTM3MTUzMGU0MTczZmViMGQ5NGVjYmM0M2UzNzgwMGI5Mjg3MWJkMGUyZmUwMmEzZDY4MSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZjdhMzA0MTUyOGE1M2U0YWZkNzNjYTBmYjVlMGUxNjQxZGY5ZThlN2I3NGFlZGEyMjYwMTc2NDI1M2M3OTY1MSJ9" + } + ] + }, + { + "id": "claim_971e76f422034bc993fd7b477c4c5aca", + "statement": "The crates.io publish job runs `cargo publish --token ${{ secrets.CARGO_REGISTRY_TOKEN }}` after installing libdbus/pkg-config (the crate must compile to publish), which fails loudly if the tag's version was already published so a re-run cannot silently no-op; only versions carried by main when the tag is pushed get published, since the tag sits on the release merge into main.", + "evidence": [ + { + "resource": "repo://.github/workflows/release.yml#L128-L141", + "version": "repo-lines-v1:sha256:c9420ffa4c7431bd7e0d80c58ff6b26b8d8ff7ef87f15429245c33ae110b59f3:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU4MjU0YzkxNmE0NDgxMjNlMmQzZDk1ZWJhYTJlNGIxZGIwZmE3YjU0M2UxMmMzZTFiMTFjZjM2MjE2ZjIwMGEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjMzOGVmZGM1MjQxNDZlNGU0NDlmZTc5YWU5MzRlMTE0OTM2MGQxZGFjMWMzNjlkZTgzY2EyMDI4MjllZTlkMWUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjJhYTIzNWQ5ZjdiODIxYzUyYzhmN2E0ZDVhOWE2NDFjMTI1ZjA0Mzg2ZjQxNWJhMWRmNWZjMDBiNGY4YjczYzUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ" + }, + { + "resource": "repo://Justfile#L337-L346", + "version": "repo-lines-v1:sha256:e0c6d25384bb913fecf4a46becbcd5da6c6131c7a32f156e1082d2e90b86b76e:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjIxMjBlZjZjYjJiMWQ0YjNlMzA5OTA1MzgwODJjODJjNWY0MWM3OTAyZjQyY2ExMTI2ODE0YzU1ZWE2MDUyNzQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRjMzA0ZjdiYmY0NzA1MjQxMTA1MTI2MTM1ZTA0Yjg3ZWRhNGRmZGJlYzNlNDBlMzM3ZjE3NDcxMjM3YmY3MWQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE1N2QzN2VmZGMyYmY1ZDY4NjQ4YTgxMjRlMzFmNjZkNDllNmUxMzFlMmEwN2IzODUyODQ2YTQyMWY4ZWNmM2QiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjAyOTRjOTk3NGU4MjhjY2ExOWYwYjBhZjY5NWMxNWZiNTZiNDQ4MTViNGJmNWNiYjdjNmQwZTliMzhlZWEyODEifQ" + } + ] + }, + { + "id": "claim_ed1d182d30b2441a9a9271591d10e583", + "statement": "openwiki-update.yml runs on workflow_dispatch and a daily 08:00 cron (`0 8 * * *`) with contents:write + pull-requests:write: it installs the openwiki npm CLI and runs `openwiki code --update --print` with a prompt that makes CHANGELOG.md the primary change ledger (audit every Unreleased and historical entry regardless of the recorded gitHead, compare against the source tree and wiki, add missing pages, update indexes and cross-links) and maintains openwiki/changelog-coverage.md as the audit notebook; it uses the OpenRouter provider with OPENROUTER_API_KEY and model z-ai/glm-5.3-flash, with LangSmith tracing enabled only when LANGSMITH_API_KEY is non-empty.", + "evidence": [ + { + "resource": "repo://.github/workflows/openwiki-update.yml#L3-L47", + "version": "repo-lines-v1:sha256:ec81a2449380b47a4971b00cdf2cbc78e9ffb2b98dc6b952379207843625b26c:eyJzZWxlY3RlZExpbmVDb3VudCI6NDUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU5NmRjNDgxNDlmNWE4M2QxZWQ2ZGJiMzliYmM3ZTJiOTAyYWEzZDM0ZDZiZDVhZTRiMjkxYjY4ZGYwY2E3OWEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjY4MmZiYTE5MWJkNDllMzYwNDM3NDdlMWU5ZTlmYmFmMmZkYjA2YjliYWJlNWY3ZDA3MDAyMzlhYTYzYWIxNTYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjoyLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImM1YjFmMjRlNjE4MDVlZTFhOWJjNzgwNDdlMWU0YjNkODYxNTdkNmUzN2Q4YWMwYmNiZWJkNWU2NWU1ZWYxYWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImEzNTc0NmFhY2Y2ODI2ODUyOGYxZDJkNjY0NDM2MTkzZDZmMDFlYmQ3MGY4NTNmMmE4YTMzYjYyODY1NmRlNWYifQ" + } + ] + }, + { + "id": "claim_81b5b6f9fe5646a2b940882fa6ccfa8f", + "statement": "The OpenWiki bot opens its PR via peter-evans/create-pull-request (pinned to the v7 commit SHA) from branch `openwiki/update` onto `development`, with add-paths limited to `openwiki`, `AGENTS.md`, and `CLAUDE.md` and commit message/title `docs: update OpenWiki` — so bot-maintained agent-rule files ride along with the wiki output.", + "evidence": [ + { + "resource": "repo://.github/workflows/openwiki-update.yml#L49-L62", + "version": "repo-lines-v1:sha256:0a5ca310457668a78955de68d9c39eb6570fbf6d93082d20724f4b7a81bb80d3:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxY2ZmNGFkMzNiYTkxYjE0OGUxYjFiODI5MGI4ZmZmMjdiMjAzZmFiZmVlNTg3MDI4MmRlMzY3MzgzYjFiZTIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQzZWZkOTM4MmYyMmZlM2VkY2I4NDkyZDcxMDlmOTIyZWQ5ZGM2M2JiZTFhZWE5OGFlZDFkMTAxMTRiYmJmNWMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImY0Nzc5NmQzMTczMmZiY2U4OTk4YTJmN2U3YjFiYjk3NTVjMjNlNTg1MDMyMGI5MDkzN2Q4MmMzZmQwZTYyYTAifQ" + } + ] + }, + { + "id": "claim_7c644904a66d445ea88624419d783a65", + "statement": "strix-pentest.yml is manual-only (workflow_dispatch with a scan_mode input defaulting to quick — no PR trigger, because Strix scans consume substantial OpenRouter credits and may run for many minutes): it checks out with full history, installs Strix via the strix.ai installer, and runs `strix -n -t ./ --scan-mode \"${{ inputs.scan_mode || 'quick' }}\"` with STRIX_LLM hardcoded to openai/gpt-5.6-luna, LLM_API_KEY mapped to the existing OPENROUTER_API_KEY secret, and LLM_API_BASE set to the OpenRouter API.", + "evidence": [ + { + "resource": "repo://.github/workflows/strix-pentest.yml#L24-L30", + "version": "repo-lines-v1:sha256:fd876619cd0f184d02319d346755f86b316350f36b1ad6c9e9cc73ab947b87a9:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiM2EzNzJkZWE3M2ExMGRkZDgwNDNjNWQ3NzVhMWQ1ZmFlNDU3MTgxYWU0OTExNjEwMmU5MGVkM2M4M2U1ZWEyYiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMGEyZGJhYTJmNDI3YzgzZGQ0ZmMzNGMxMDkwZTdiMGUzMzRjYThjNGE3OGQ0Zjc2MTVlZTBjYzEzZGMxZGMzMSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiN2EzN2U1MDRhZjM5N2JiOGFiMTJjODVmMzQxN2Y4M2YzMjZkZDkyNDBhZDQwMGZkZWRiZjQ2MTcwNWJkZTgyYiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjAsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZTNiMGM0NDI5OGZjMWMxNDlhZmJmNGM4OTk2ZmI5MjQyN2FlNDFlNDY0OWI5MzRjYTQ5NTk5MWI3ODUyYjg1NSJ9" + }, + { + "resource": "repo://.github/workflows/strix-pentest.yml#L3-L12", + "version": "repo-lines-v1:sha256:7953d856952044c476d07d9b63504e9f10a0c5d0b4c91cc49369cfc782a77145:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA5ODNjNDJkYWNiZTUxOWE3OTVmNjRlNmYwNWM0MTdhNmQ4YjNkY2IzZjIzNTZlYWI4YjYyMTgxZmZkOWFhYWMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjoyLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNmZjA1NThhZTI4NDAyNmQ4ODNhYjQ5N2RkZjY3ZGZlYWZkMGU4NmNiOTc2ZTQ3N2VlM2IxMzJiY2QxYzU0YjAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImM5OTg1YTE2YjkzYzRmMjIyMDc5NDdmNWNhNmY1NTZjYTY5ODNiOTQyOWRmYzg2OGU5YTY3OTYwNDRkYWU5Y2IifQ" + } + ] + }, + { + "id": "claim_e775d01a26234db2a47c64ee86218b71", + "statement": "The four workflows consume exactly three repository secrets — CARGO_REGISTRY_TOKEN (crates.io publish), OPENROUTER_API_KEY (wiki bot provider key, reused as Strix's LLM_API_KEY), and LANGSMITH_API_KEY (tracing) — while npm publish needs no long-lived token at all (OIDC trusted publishing) and STRIX_LLM is a workflow env value, not a secret.", + "evidence": [ + { + "resource": "repo://.github/workflows/openwiki-update.yml#L41-L47", + "version": "repo-lines-v1:sha256:a36daaea5d9cb2d3815b0f8043244941e2008f32441076a9d94df4391014edf0:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiN2IwNzg0OGE0MzY2NjU2YjJiYTYxOTdiZTAyYjkyMDgxNDU0NmY5ZDlhZDBiNTMzOWU0YzI2YTJjOTQ2NTExNSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNjgyZmJhMTkxYmQ0OWUzNjA0Mzc0N2UxZTllOWZiYWYyZmRiMDZiOWJhYmU1ZjdkMDcwMDIzOWFhNjNhYjE1NiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNjY1Njc2ODA2MTc5YzIyYTE1NmNkNzY5NTdhMWE5ZmYxYWFkNjAxODJiOWViMzg2ZmM2ZjgxNDNmYmJjOWUzMCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiYTM1NzQ2YWFjZjY4MjY4NTI4ZjFkMmQ2NjQ0MzYxOTNkNmYwMWViZDcwZjg1M2YyYThhMzNiNjI4NjU2ZGU1ZiJ9" + }, + { + "resource": "repo://.github/workflows/release.yml#L136-L141", + "version": "repo-lines-v1:sha256:23066db526185ea975d381af0c0dc1ede133126a0005914f8be40775e73018b9:eyJzZWxlY3RlZExpbmVDb3VudCI6NiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiOGVmZjlhOTQwN2JmNDFkYjUxNjM0MjAwMzkxYWYzNTI0ODM1YzE4NzQ3MWUzNGM0OTdjYjdmNDdhZjczZmE3NyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMzM4ZWZkYzUyNDE0NmU0ZTQ0OWZlNzlhZTkzNGUxMTQ5MzYwZDFkYWMxYzM2OWRlODNjYTIwMjgyOWVlOWQxZSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZDdmMDhmNzMwYmZmNWRjMmE2ZmRjMTU3MmE3MTAzZTk1YWZhZTRhODVlZTNlNDliZTZiZGMyYWUwMThjN2JhZSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjAsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZTNiMGM0NDI5OGZjMWMxNDlhZmJmNGM4OTk2ZmI5MjQyN2FlNDFlNDY0OWI5MzRjYTQ5NTk5MWI3ODUyYjg1NSJ9" + }, + { + "resource": "repo://.github/workflows/release.yml#L91-L108", + "version": "repo-lines-v1:sha256:083b9c6cb57c18684c47bd16bf3b4e7580dd4a9366aa5e0d537d4a78a455f940:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQyOWNiYjFkNDgwNTQ2NGY5NjFlMjA3MDYyNWFlN2EzMjBiZmQzNmI3MWZhZTIyNzgxZTJiYWM2NTRiMDM0ZDQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNjYTA2YTQ1ZjQyNTlhNzM2ZjQ1OTRjNjliZTM1Y2FjZjcyYTZjZjhjZTFmNTE2N2JjN2Q5ZTg3ZWJkMGFhODAiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjRmYTgxYzMwOTdkMGFjNzY0MGM0MjYyMjJlMTY5MmM2ZmIyYmM5MTRiZDRhMDQzMGI2ZWUzYTdmM2Q1ZjkxMDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImJjZWNkOTE3OTRhYmMzNDg5YWQ3MTVlMDc5NWFhN2RmZTQxMDQ0YTcwYTBiMzI4ZmRjY2RiNjdmMzZjMjQ3ZjAifQ" + }, + { + "resource": "repo://.github/workflows/strix-pentest.yml#L24-L29", + "version": "repo-lines-v1:sha256:ebda3a338ecf1069068d082cc4851e700a10e4c64bf4da133667986494b90f75:eyJzZWxlY3RlZExpbmVDb3VudCI6NiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiM2EzNzJkZWE3M2ExMGRkZDgwNDNjNWQ3NzVhMWQ1ZmFlNDU3MTgxYWU0OTExNjEwMmU5MGVkM2M4M2U1ZWEyYiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNjk2NTg1ZjA1YjZmNDgyYWRmNGE5YmFiNzZmMWQ2NDUyNDU0ZmM0NzM3OTg1OGYxN2Q1MTAzMzQ5ODk4ZmI4MiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiN2EzN2U1MDRhZjM5N2JiOGFiMTJjODVmMzQxN2Y4M2YzMjZkZDkyNDBhZDQwMGZkZWRiZjQ2MTcwNWJkZTgyYiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjEsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMGEyZGJhYTJmNDI3YzgzZGQ0ZmMzNGMxMDkwZTdiMGUzMzRjYThjNGE3OGQ0Zjc2MTVlZTBjYzEzZGMxZGMzMSJ9" + } + ] + } + ], + "verification": { + "by": "openwiki/0.5.1", + "at": "2026-09-11T12:46:43.817Z" + } +} diff --git a/openwiki/.claims/security/secrets.json b/openwiki/.claims/security/secrets.json new file mode 100644 index 00000000..9c06fd47 --- /dev/null +++ b/openwiki/.claims/security/secrets.json @@ -0,0 +1,438 @@ +{ + "schemaVersion": 1, + "pageVersion": "sha256:28fee48689374f42a2a1f48da108caac72ac9c743d0108262e524386154483f7", + "claims": [ + { + "id": "claim_06d699e15be045ba9dc0388e3e5a9ee3", + "statement": "Host passwords and identity passphrases are stored in the OS keyring via the `keyring` crate under service name `sshub` with keys `host:{id}` and `identity:{id}`; `NamespacedPasswordStore` prefixes keys with `profile::` so separate profiles never share entries (compat mode uses an empty prefix), and SQLite persists only `has_password` flags, never secret material.", + "evidence": [ + { + "resource": "repo://src/credentials.rs#L214-L220", + "version": "repo-lines-v1:sha256:6561411fedc53691ca7b9e50e1068f8221a024e9e3c9730b911361f2222308f6:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiY2ExMzdjNzc5NmM4NmFlNWJlZjZkNzQwNzQxNGE0YWYyOTg2NmJkNzhjMzY1ZWNjNzJkNGJiMzUwOGRjMDU5ZSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMTllNzYzNTVjZjExOTk0ODBjM2Y5OTgzNmIxNDg2Yzg0MWQ4OTFmYWFkOTA0YzYwOGUwOTMwMjJkMzc1N2FlZSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiN2JjOThkNmIwZGM0OTJjNTAxNmRmMTA4MDVhM2E1NGViOWMxODRhNWE2MTM3ZTdmNGJlY2ZhMGMyMjA1NTBkMCJ9" + }, + { + "resource": "repo://src/credentials.rs#L5-L44", + "version": "repo-lines-v1:sha256:765d997fbbaf798c7574d5d8ccccb63e6ade1cf3d63bc6ec11483831d3747c2e:eyJzZWxlY3RlZExpbmVDb3VudCI6NDAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImZhZGU3ZTk3YzYyZWI0NGIzY2M0NmE4Njc4YTNkMzc4MDYyOWM2YTkxOTMwZDYyZWQyMjkxYzQ1ZTA4N2ExNjciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjA0NGNjNWQ2MzliMjViMGQ5NWMyMDk5NDRlNmIzMTk5Njg4OWNiMzNiNzdjMjk3MDAwNmJmMmM2NWE5NzFlNTciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjRmNzdlNTAwMzNiNjE3ODIwNzc5ZWZlMDRmN2YzMjY0NTEzOWQ1ZGI3OWIxMmUzMGFkYTRmNWVkM2FlYmEyMTgifQ" + }, + { + "resource": "repo://src/profile/mod.rs#L291-L299", + "version": "repo-lines-v1:sha256:8c82f0f66a861b4405706a5dec31015bbcf11cc973726b677beaadaa3b4bcfa4:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZGMyYzA5NWIwNjQxYjhkNzFlODE1ZmYxYmJlNWY4NzhiMjU0OWMxNGFkNTczYmU1MjYzNWE2ZjlkZjRjY2Q3MSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYjY0MzRmYWQwZDI4YzZkOWVkYzg3Y2IzYTIzMDU5YTU2NDcwZDdmNjU5ZmFkN2Y3MGYxODA4NmI4ZDY3YTAxYyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZTBiMmMzZmE5MzM2MDcyODU4MmQ0NjZlZDVlYTMzODNiZjk3MThiMzUxNDQyYTg2MzlhNDI2MmVkN2ZlZWE0NCJ9" + } + ] + }, + { + "id": "claim_a75845ac5121455facf656e84ae89ede", + "statement": "Cargo.toml enables real keyring backends (apple-native, windows-native, sync-secret-service, crypto-rust); without a backend feature keyring 3.x silently uses an in-memory mock that works within one process but persists nothing. `check_keyring_available` probes the Secret Service and treats an absent org.freedesktop.secrets as unavailable; without a provider SSHub warns ('OS keyring unavailable. Using credentials.json fallback.') and ssh falls back to interactive prompting.", + "evidence": [ + { + "resource": "repo://Cargo.toml#L51-L61", + "version": "repo-lines-v1:sha256:79c366c7a7f9b581bc52d6378f36377678b0b884c147e1457dfee31dc54deace:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU4NmQ5YTNiYTJlMGI0YTg5MzkyMmU3NGE2NzgzYjhiOWExZDE4NWJhYzUzZDg2N2NiZDFkOTVhNzE4MjdkZWIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNhNTk4NGI2ZGRjYmJlYmM1ZWY2ZGViZTc4MTU3OWE0ODQwN2ZjNmM1NjNjNTc3ZjZjOThlYzdhNGQ2M2JhNWEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjliMDA4YTkzY2U5ZDJlYTU2YmQ5Y2FlMzc3MDdkMTU1NDM0Mzg5NDk0NmE4MjU2NWRlMzU2YjhjZjcyZDJjMjUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjViZDk5ZWMxMTcwN2I1MDdhYjZmODc3OGU3YTc4MjZkOWY4NzI1YTRhYzRmYmY5M2Q5MDIxMzc4MjhkOGVhMWYifQ" + }, + { + "resource": "repo://src/app/mod.rs#L829-L859", + "version": "repo-lines-v1:sha256:14a94249579977f3a621ba472888eba486dc3b06dd6a6f7397dd21868ae3c909:eyJzZWxlY3RlZExpbmVDb3VudCI6MzEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImY2ZmQyOWQ5YWU3YmU2MjE4NjIxNTIxNTgzYjFkODRkMWQ3MWVkNjJjYjk1NzhmMGZhMDY2ZDJlNmNkYWRlOTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImZkZmE1MmEwMzEzMmE0OTk2OTc5NzAxNDY0MGI4YTkzMzFhYjE4YjllYTIwODNiNTM2Y2NiOGIwZDc5MjMzMmMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjU3ZmRhZWI2NTRlM2MyNTU5NDJhNzYzZTg0NjA5ZmIzNGFkZWRmMjU2NTAzYmRjMWFhYmMwNDdmMTE5ZTU0MmEifQ" + }, + { + "resource": "repo://src/credentials.rs#L46-L62", + "version": "repo-lines-v1:sha256:dbd0a676c1511f1dafe39a36d968e95334a583dd59af51050fac2e6e2033428f:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjhmM2JjYmQ4MjUxN2M0YTY1NDVmMWRmMTdhOWQ1MWVmMmJiZDc3MjczNzZlYWFiN2E4MWJjNmVmMjEzMTE0ZTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjU0YTFjZjhiZDczZGNkMzAxNTRkYzZiMjE1OGY1NWY0NWVlOWZiMGFkNjY1NjJmNmJmYzVhM2Q5NGZiNmYzYzYifQ" + } + ] + }, + { + "id": "claim_88024dbbf76c40b1ab790ea2a83840cf", + "statement": "Termius import re-stores secrets with write verification: `store_credential_verified` sets the credential and reads it back (rejecting silent drops and value mismatches), and failures are counted in the report's `keyring_failures` and surfaced in the TUI notice instead of dropping secrets silently.", + "evidence": [ + { + "resource": "repo://src/app/import.rs#L90-L116", + "version": "repo-lines-v1:sha256:45109be978ffd7aca7bdb5d18ebad6f7f3348fcb033f8973927be2d08aa1171f:eyJzZWxlY3RlZExpbmVDb3VudCI6MjcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjEzODRmY2ZlNmZlMWI1ZjIzZWYzOTIzYTVlN2Y0MDEwNjhiYWViZjcwNTVkYTIzMjUzY2U5ZThiNTEyMmEwMjQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU1MjJjNDJjZDE5YzZkMDNkODA4YzQ1MjcyMDU1MjgyNWI3ZWExYzViNDkxYjIxODUxMWNhYzc0MTVjZjlkOWIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRhOWY4NmQyOGU3ODRmOWZkMzFmYjcyZDE1ZjYwY2JjZWFhZmRkNjY5Y2JkNWI2ZGE3ODg4YTAyMzMyMWViYjkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxNTgwOTczYzgzNmY4MjkyN2YyOTlkYTI4MjNmMjU0MjMwZDU4ODdiYjljZjAwMzAxZDdjZDlkOWZlZDI4NzUifQ" + }, + { + "resource": "repo://src/import/termius_csv.rs#L270-L355", + "version": "repo-lines-v1:sha256:5a4b9d9c69a80eee2c0dc8a454d8f9334bda0694c4c1f95267e0013559f66a95:eyJzZWxlY3RlZExpbmVDb3VudCI6ODYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE2ZDQyYzk2MDhhZWFmNjkxOGU1NzJjNzEwZGViYmNhYTlhNzk1OTQ0ZDM1MDQ2NzY2ZTM1YTAyMjgyNDRhNTYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjAyYzczZjg5YjU1NmJlZDFjZGFjYzJkYmM1ZGVlYWVmMzM0MjExNzEzNTNkOGIyOTI2NGQzNDNmMDNkMzg2MjAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImM0Yjk0ODY2MTkxN2JiMmUzYTBlOTNhYTVjYmIzZmVlYzc1YmVkZmY2ZmY3YzQ5NTEwODAwOGI3ZDMzMDQxNzcifQ" + }, + { + "resource": "repo://src/import/termius_csv.rs#L357-L377", + "version": "repo-lines-v1:sha256:db0145194d38b0bea35b07e2ec8fa86b7623ca39e09b22488204d1a6e0b417d3:eyJzZWxlY3RlZExpbmVDb3VudCI6MjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA3OTZmNjdlYzY2OGViOTViYjc4NzBjZGI1NjgyMzY0NTk3Mjc2MjUyZDJjYTg3MmI0YzBkMmU2MDNmYjhiNmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQ4NjcyYTJiZTE4MDY1NDJmZmE4MDJiMmNiNGFjYjEyNjVhMTU5ZmVjMTQ2YjBlMjk3NzYwYjRmMzg1NWU5NjkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJjMzk2MTFiNWQ3MzBhMDFkNmFkNzc4ODk4Nzc1NjcwNjEzNDc2ODlhZjk0NDI2ZDIwNGVjZThhZjllMTI3NWIifQ" + } + ] + }, + { + "id": "claim_521bf8245a6344de9dccef4052fe9ece", + "statement": "`sshub db purge` wipes only the launcher SQLite database (plus sidecars) and deliberately leaves keyring entries orphaned — the output says 'Passwords in the OS keyring are left untouched'. Deleting a whole profile does the opposite: it captures the profile's namespaced host:{id}/identity:{id} keys from the database and removes them from the credential store, but only after filesystem deletion commits, so a failed delete can restore the profile completely.", + "evidence": [ + { + "resource": "repo://src/lib.rs#L70-L93", + "version": "repo-lines-v1:sha256:c6d09b2f202f7cde9a970c98f8d3b2830cd9fedd1bc53e15d3d7565ec0ebc474:eyJzZWxlY3RlZExpbmVDb3VudCI6MjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImIwN2JmYzEwMTI4ZjU4YTc0NDAwMjAzZTQ5MzZiZTU1Mjc5MGZkNmUyMmU5ZjU5ZTEwYjFkZDQ0M2QyNzIyYjciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE2MjJiNzAyOWRjYTAxNmJhZTI5ODZmNWU4MDI2YjNkM2E1ODYwNGM3NjM1ZGQ3OTRiYmQ0YTgyM2MwZWRlMDEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImVmYTY4ZGFjOGQxMjA5ODVkMGE0OWI5NTYzNjRhMmNlY2UyNGZkYmQ4YTJkMTY3ZjBjM2E3NzI1ZmNkMmIxY2IifQ" + }, + { + "resource": "repo://src/main.rs#L117-L148", + "version": "repo-lines-v1:sha256:53613117a219ad6434485707e8a91abe397522de7620c1d1eddde1bef6aa1407:eyJzZWxlY3RlZExpbmVDb3VudCI6MzIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE4Y2FjMzg5NjExOWM4MDczYzU3OTBmYmE3MWYwOTlmZDFlMjgxYjUyNTY2ZmEzZjhiZTNlNDFkMjZlN2U4ODEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjU5ZDZhNTBhZGY4NTc1NzFmOTgwM2E5M2U0YjM3ODUxOTQ2Yzg5MGE1OGRjM2UzYjIwNTRmMjVmMWJkYjQxN2UifQ" + }, + { + "resource": "repo://src/profile/mod.rs#L796-L868", + "version": "repo-lines-v1:sha256:a001ae0db8148317647f53e6a6aea1ad32d459a04c7e3a0dc04f377949859eb1:eyJzZWxlY3RlZExpbmVDb3VudCI6NzMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNhMWI2NGZmZjM4ZTZkYjk1NDk0MjU1NDI1NWRmNzJiYjY5NTJjZjZjN2Q4YjBmZGZhODk2NTcwNGIyMjUwNWUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjEwN2ZlYjViZGEyYzQyZDg2ZDY1NzMyNmZiMzcxOTg3NDc4YWZlZmUxYjc5YzhlNzhhYjU3NDE1MmU5ZmZkODYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjdiYzk4ZDZiMGRjNDkyYzUwMTZkZjEwODA1YTNhNTRlYjljMTg0YTVhNjEzN2U3ZjRiZWNmYTBjMjIwNTUwZDAifQ" + } + ] + }, + { + "id": "claim_eae2182dfec74b99af8f63757608e98d", + "statement": "When no OS keyring is available, credentials fall back to a plaintext owner-only `credentials.json` in the data directory, written via a 0600 temporary file (symlink refused) and atomic rename; the status bar reports the fallback. When a keyring becomes available, `migrate_fallback_to_keyring` moves entries back on the next launch and removes the file only after every entry succeeded.", + "evidence": [ + { + "resource": "repo://src/app/mod.rs#L831-L844", + "version": "repo-lines-v1:sha256:dce9bfd6390e33e7473e3e32780212e70ef87429c328a4e4571f712f822dd023:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFjZTYwNmU5NWFiNzYyMDRiYzkxZTA5MzE0NzdlOGY4MTVhOWQxZmYwZWE5NzBmNGIwMjhlNTFhNGZkOTM0NzYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImM1Y2RlZjgwODVhZTc0YWI2MzZkNjQ1OWY1YzZkNWZiZmIwZGIyNjRmMWIwZDc1YmFjNTE5YzliM2Y2NzE3YWMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjAxNDFlNDY1OGE2NDlmYjdkODk5Nzc4NjFiZmI2ZmVkODQ2NWY5ZTk0NWUxMTU0Y2VlMmM0NjllNjgyNGRiNGMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjUyYjZlY2E0NjY1MTdlMDc1YzViZDI0YmMyN2ExNDdiMGE5Y2U4MzE5ZmY5MWJkOTBiNDNlMjhkYjcxMWE0M2IifQ" + }, + { + "resource": "repo://src/credentials.rs#L113-L198", + "version": "repo-lines-v1:sha256:48f677a1633738cd01d390b7d673f5a83cff5f65fbbe780732b25b16edd2dbbb:eyJzZWxlY3RlZExpbmVDb3VudCI6ODYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjZjYzg1YTE3OWRkMDhiZWUzNWU5MDBhMzc0NjhiOTVjMmY4NzBlZWU4ZGQxODQ3ZDFhYzE4ZGFjMjgwOWQxZTgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjUzZDU0NTMxMjA0ZjNmZDdjZmMyMGQ4ZWIyODU0MGM0ZTY5N2Q2ZTQyZDM5ZDIzNTc4YTM5ZTRiMzBjODdlZjUifQ" + }, + { + "resource": "repo://src/credentials.rs#L64-L85", + "version": "repo-lines-v1:sha256:e4633db378c372ee136ac05f6db2c569fa62eb18b2328f40036e93644d5497bd:eyJzZWxlY3RlZExpbmVDb3VudCI6MjIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQwNDYzYTE4YTU3MGVhNjFlNzcwNzk4Y2YwMDUyMWU0OTA0MDBhYzEwMWU3NWVkZTZlOTEzZTJmZmFkNDNmZjAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjA2MjA1OTgzNTgzMTc2ZmY3ZTY1M2ZkZTMyNjc5NzIzN2UwMTk0ODA2YzkzYWY4NTZkMzE0YWVmMGZhMmE0MmUifQ" + } + ] + }, + { + "id": "claim_6fe3791e176043caaf43d11303a6df3d", + "statement": "Host/identity form secret fields arrive prefilled from the credential store and masked. Ctrl+R (KeyAction::RevealSecret, rebindable) reveals and copies, Ctrl+Y (KeyAction::CopySecret) copies without revealing; the hint row rides the password field while focused ('Ctrl+R: show + copy | Ctrl+Y: copy'). Both notices name only what was copied — never the value — the reveal is ignored away from the secret field, and leaving the field re-masks it.", + "evidence": [ + { + "resource": "repo://src/app/host_form.rs#L341-L356", + "version": "repo-lines-v1:sha256:a65135818b49b763f377d658be04bcdd9ac9988c0494ccb9597265af89c7f880:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFlMTA2YWI3NDA3ZDBiN2IwYTFjNGRjNjMxN2I2M2I5ZDc1MzFkMDc3MTJmZTdkYzFhNGEzNDZhYTZjZDAwOGMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUyZmUwMDllYzBmN2FhYTc5ZWY4ZmU4MTdiNDFmMTNlMTQ4M2I5MTg3YzExYzIwNjY0NmQ0MWVjOGZlNDYwMWYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImVlOGI0YjhlNDUyNzJhNDQxMmI0NGVmYTBiOWUxYzdkMzEyZGRjNzY2MTQ4YmVlZjA0YTk1OGU1OWJhNjZmYjMifQ" + }, + { + "resource": "repo://src/app/tests/host_form.rs#L219-L283", + "version": "repo-lines-v1:sha256:67a10f24737924993af32d3375f12045ba547c9b884ffe501688c0ce907156e2:eyJzZWxlY3RlZExpbmVDb3VudCI6NjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRmYjY4NTRlYThlOWE3YzE4MTk0YTNhNWNiMzI5ZGY5Y2MwYTM5MTNhZTkzOGIxZTgxMDAwMTQ0YjQ2NTg2MTgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImM4NTE1MjU2NzU3NWJjMmY3ZTZiZmJhM2JmZDViNDEzYTc4Mjk2YmEwZDVlZGJlYjg1NDViMTc5OTRkOTAwOTMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImM2MDQ4YzU5MGM4ZWRmMWFiYTdkYjFmMjYzM2M0MDAzNTc3M2NiMGU2NGNmMmRhNmUwOGJhOWZlYWFmNjJiMWEifQ" + }, + { + "resource": "repo://src/app/util.rs#L301-L322", + "version": "repo-lines-v1:sha256:c69bbbfe202adf84221cf121384e29f39b508f626696f2acc447f35dce956c0f:eyJzZWxlY3RlZExpbmVDb3VudCI6MjIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImEwZThmMGYxODE5ZmY3MDNjN2QzY2ViZDUzZjAxOTk5MDIxNmE4YTkxOTQyYTY3N2M0NDgxNDY2NDJkYzY2ZjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE0MDc0ZDRlM2FhYzJhM2Q3ODY5YWM3OGE2YzU2N2QyYmM4ODEzM2ExOWMxNjczNmQ5ZjBlNWQyYTVhOGEwNjgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjVlOGFkMTQ1ODRlNWQ1Y2YyNjc1ODQzNDE5MWFhZGI0MGE5ZGE3YTJiMTY5OTUzZWYzZmI4M2JjNGRhNTZkYTcifQ" + }, + { + "resource": "repo://src/keybinds.rs#L461-L462", + "version": "repo-lines-v1:sha256:56c1519f4e7ab11d71a28c9f13576eaebbfcf6d6c891d2ee8be7b21286447fdf:eyJzZWxlY3RlZExpbmVDb3VudCI6MiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYWU0YmUwNmZjYWM2MTJhNjU1MDgwYTBhYTg0M2E1NDVkN2RmZjNhOTJiYmM1MTExODFkMjk0M2ZmYjAyNjc1YSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNjA2NGU5ZmFkNGRlYzc1YmFkMTg3NDcyY2IwMmQ3YTk3MGE5OGY0OGMwOTAwOWRlZWQ4YTA5YWRmMmVmMGJmMSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMDJkOGZjMGEyYzhlNDVlZTlhZmE5ZDM5MzVlZDY0ZDcyNDlhODNmMGY2YjhiYjg1NzFhYjdkMWJmZmY0MmUwZCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiY2YzM2U0MDAwMTAzYTQ3ZmZiNGYzMmZkYTE2ZjFlYzYwNjEyZTRhMzcwMzA2M2NjMGRmZWQxNjIxOTcyM2UwZCJ9" + }, + { + "resource": "repo://src/keybinds.rs#L718-L726", + "version": "repo-lines-v1:sha256:2b7555d7336ea6be61496eef79be384bcfaf9806a150b2e4a9f38bda8fa23240:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNDFmNGVmM2NjNmJmM2M3YjhhYzdkZDk5NGQzMDdlZDA2MzZiYmVlZWZlNmVhYmU0MTJlZTRlMzRmMWQzNGQwNSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZTRlNzc5OWUyMDUwYzE3OTU0YzdkNGI4YjY2ZjgxYmY4MTgwNzEzMTYyMDA4NDdlMjJkZTRlYTUyZjViZDcwNiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMTQ2NGEwYmIyZTNiNTdhNmZmNDQ5OGEwY2ZiYTc0N2M1NzQ4OWNmYWFkZDI5YTA5NmY1YmM5NzBjMzMyYzIyMyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiOThmYzFjOTczN2NlMGU2OTg0ZjYxOTFkZWIzYmE3ZjRlOGMyZjQ1Mzk1NGZiYzY2ZmVjYmM1Yjg3ODYwNmIwYiJ9" + } + ] + }, + { + "id": "claim_c52cea2b3ac9431fbba197cefa011bff", + "statement": "Saving a form compares the secret field against what the store held when the form opened (`password_original`); a cleared field explicitly deletes the stored credential through `put_secret` (empty value → `password_store.delete`), so an emptied field means 'there is no secret any more' rather than 'leave untouched'.", + "evidence": [ + { + "resource": "repo://CHANGELOG.md#L597-L601", + "version": "repo-lines-v1:sha256:6a6e0bcf2496171a4fc487c73419c4ab136baae74be7f6dde5621b7975b2224b:eyJzZWxlY3RlZExpbmVDb3VudCI6NSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYTRjNDM0MjYzYzFkYzMzM2UwMTdmNzhlODI4YTM4YjhkMWU4M2UzNjYxNWFmZjM5ZjZhNzE2NjQxZDhhYTE3ZiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZjJmNTg3NzQzYjU0YjllZDUwM2M5OGIwNWY2ZjY5ZGYwZWQyMTZiYjE4NTI1NmQ5YjE2ZjRiMDFkY2FlYWQxZCIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiN2E2MGY2OWQxMGE3ZmYyM2U1YzdmZDE3YzdmOTAyNzI5YTI5NjUzYjEwYzczYjM4YmU0MTUzZDAwNzg1ZGQyZSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiOTM3ZjQzYjkwOTFhNWRiOGYxMWQzMjEyMTNiMWJiZDRkYjk5ZTg4Mzc2NWMyZmE3NzI0NTA5OTQ4MWIwOGQwOCJ9" + }, + { + "resource": "repo://src/app/host_form.rs#L174-L195", + "version": "repo-lines-v1:sha256:dbf1a5516998f8b7e2c625886f19c6e10f46b2b79b928bdd9ae935d6aa6d14d5:eyJzZWxlY3RlZExpbmVDb3VudCI6MjIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRlMDAwYTQzOWQxYWU5MTkyODBjNGYzMzVlZGM3NjBmMjgwMDY4NTY2OTg4Y2U1NDgzNzBhMzk3YWM2ZTc4MjQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU1MjJjNDJjZDE5YzZkMDNkODA4YzQ1MjcyMDU1MjgyNWI3ZWExYzViNDkxYjIxODUxMWNhYzc0MTVjZjlkOWIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRhMzIzZmFjYzc2ZDgzMTA5Mjk3YWE2MjNiYjZhYTc0MzRkZGQxMTFjZWZlNzNiZDFhNWNmZGE0MzliODNmMjMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjBmMjZmNmEwNmJhOTkxNDgwYzI1OTE5YjM3MzkxYmMwZDIzNzA3OTU3ZmI3Y2UzZGUxNzJiYmMyNGQ0ODNmNzgifQ" + }, + { + "resource": "repo://src/app/mod.rs#L516-L537", + "version": "repo-lines-v1:sha256:424cd25e2757b2b9828b09e3fe47fb42bd4c83d4f7802959102fad6915d6c7ce:eyJzZWxlY3RlZExpbmVDb3VudCI6MjIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQxZDM2OGU2YzA0YzYxNjUwMDU3NGUwZGFmODA5OWJhMTI5OWNlYWZjNTBkMzJhYjYxNzRlNWQyOWExOGYwMzAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImZjNTIxYjM4MDZmY2U5M2E4MDg0NWYxNTkzMzgzMzVjNjkwNWFmYjE0NTVmNjgwYzM3NDE2ZDkyM2I3OWIwMjEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE4NzE5YzYzZWU2MzY1NDYwZTUwNmM3NzUzMWZmZmQ2N2RlNjQwMTk4MTU2MmY5OWQyY2ZjNGIxYzNiODY2YWQifQ" + } + ] + }, + { + "id": "claim_b9432bb898c149cba88e37938ab5943a", + "statement": "Secrets reach ssh via SSH_ASKPASS staging: the secret is written to a fresh 0600 file (`sshub-askpass-{pid}-{n}`) under $XDG_RUNTIME_DIR or temp dir; the child gets SSH_ASKPASS=, SSH_ASKPASS_REQUIRE=force, SSHUB_ASKPASS_FILE=; main.rs calls maybe_run_askpass() first so the re-executed binary prints the staged secret and exits before touching argv or the TUI; the file is removed on Drop (so a SIGKILL can leave a stale file).", + "evidence": [ + { + "resource": "repo://src/main.rs#L8-L13", + "version": "repo-lines-v1:sha256:401e8e4452ae833ce394a7c89ba3b92f224ae1dca6a0968817270105ca5a97d7:eyJzZWxlY3RlZExpbmVDb3VudCI6NiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiY2NkNGQ2OWQxMTA0M2M2Y2Q5M2M0YmM2YTlhZDViNGU4MTFkODI3N2Q5MjQ1NWVlNmQxZTY2NjQwZTllYjUzZSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYmJhYjg2NGFlMmY1YzE4ZGE5ZDE1ZGVmMDBiZWY5YjFmZDU5MjFjOGZlOWI4NDk4ZTBhNGRjYTgzNzgxNDRkYiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMWYyZTUyNzcwZDI4Yjk1YzY4ZjRlNDBhOTI5YzI1ZTRlNTdjNTZkNWFiMzZmOGI4YmU0NTMzNGMzOTdiYWUxNyJ9" + }, + { + "resource": "repo://src/session/askpass.rs#L17-L54", + "version": "repo-lines-v1:sha256:01c21f744943c9bb59c3f3e28f0e8fb88d6a86505ce0758725a8205dd2a4726f:eyJzZWxlY3RlZExpbmVDb3VudCI6MzgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjczYWM3N2VjMzRhOGZiOTE4ZDExNjUxMzZlYjY4YzMxNzc0NjhkYWE1Yjc0NjRjNzZjY2Q4ODJmMzA1NmFhOTIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImJkODU0NTUxOTE5NTdhZGIwZmFiZGRlMjA4MzJkNzBjYjczYTcyNzEwMmFjZWMyZTcxNzVkYmU5NTE0OTUzMDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQ5NDEyODQ5NzhhMTliN2Q1ODJkMzlkZDVlNTNhNDVlYzgyMDQ5MmVjMTIyZjFiMmFjOTMwNWJlMzBiMjljNTQifQ" + }, + { + "resource": "repo://src/session/askpass.rs#L56-L119", + "version": "repo-lines-v1:sha256:2893684e886728fe023f85f7130667e79271d385b4465f60f4e461a6edde91e0:eyJzZWxlY3RlZExpbmVDb3VudCI6NjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVjN2Y0ZTU1Y2E4MWVhNTliY2NjNmFlMjM4ZDUxYWNhZWQ2YjFlNmU1MjUwMTM4NTlhZjc1OGEzMmZkZmFmMDkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjZiYTk1NWJlZGVmOGRjMDZkYWRlNGYzYzRiYjg2MzcyODY5MDBmMTExOGM4ZDgyNTk0YzIzN2QzNTYzMmI2ZGEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjdiYzk4ZDZiMGRjNDkyYzUwMTZkZjEwODA1YTNhNTRlYjljMTg0YTVhNjEzN2U3ZjRiZWNmYTBjMjIwNTUwZDAifQ" + } + ] + }, + { + "id": "claim_d4b8b96050624708a637d126b5b12cb8", + "statement": "`helper_exe()` re-resolves the askpass helper after an in-place upgrade replaces the running binary: `/proc/self/exe`'s ' (deleted)' marker is stripped and the path retried, so password auth keeps working without a restart (cargo/npm install unlink the old inode). A genuine helper lookup failure is written to the session log ('SSH_ASKPASS unavailable — will type at the prompt') instead of surfacing as a misleading server-side rejection. The same mechanism feeds sessions, tunnels (`stage_tunnel_askpass`), `sshub exec`/`connect`, broadcast runs, and the OS-detect probe.", + "evidence": [ + { + "resource": "repo://CHANGELOG.md#L579-L587", + "version": "repo-lines-v1:sha256:8011e000e5719e2a6c59580237233cf94babbee9303431303768f27915208f38:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiM2ViZGQwZmQzODdmZWMyYTdiNjcxZjEyZTZjMTc0MDY3YWFiYmU3NzM2OGY3Y2I1ODViNGJiOTlmMGUzZDMyYiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZGI4ZDU5YmEyNDRkYThjZTgzNGI3NDI2ZTRhNTM4MjJlOTJmZDVmZjJlM2RlMzU0YzhmYTgyMWE0ZTVlMTIzMiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiOTRiYjQxZThkNTA3ZDAzNDU5YmI0MzYwOTkyZGIxZDk3YzBmMTZjZjNlODkwODRhZjZlZjM1ZDk4ZDA0MTgyYiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiODViOTA3MGI4ZDg2NmVjZTAxMmQxYjQ2NjkwYWQ1YWEyNjY2ZGI0NjJlMzg5MjI5NDExOTE4MzUwMDg4ZjkzNSJ9" + }, + { + "resource": "repo://src/cli/host.rs#L201-L209", + "version": "repo-lines-v1:sha256:b9f3a837d40af7c04a0e0931f3e6d78fbfc6073aa3fbaf2f0b3f5e195a0cf9c1:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYWE1N2Y3MTFlN2MyMmI5YWVmZDE5MmEzZTc5MTcyYjE0NWE5ZmExZTkwZjI1OWM0YWQ3ZDdkYmU0YmI1MDhhMSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYjU1MjRkNzhmNDZmOGNmYjc1NGRlOGQwNjhiNjdkOTFiMDUxZTE5Mzk3MDc4Y2I2MGNhYWIzMjZhMTk5MjNlYiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiOGVhOWZkOGMzMWNkOTk4ODY1OTQ1YmQzNjAzM2RjYzY2NjU4NjYwOThkNzczNzZmYjk2OWIyNWI0NzJlOTYyNCJ9" + }, + { + "resource": "repo://src/session/askpass.rs#L17-L54", + "version": "repo-lines-v1:sha256:01c21f744943c9bb59c3f3e28f0e8fb88d6a86505ce0758725a8205dd2a4726f:eyJzZWxlY3RlZExpbmVDb3VudCI6MzgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjczYWM3N2VjMzRhOGZiOTE4ZDExNjUxMzZlYjY4YzMxNzc0NjhkYWE1Yjc0NjRjNzZjY2Q4ODJmMzA1NmFhOTIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImJkODU0NTUxOTE5NTdhZGIwZmFiZGRlMjA4MzJkNzBjYjczYTcyNzEwMmFjZWMyZTcxNzVkYmU5NTE0OTUzMDAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQ5NDEyODQ5NzhhMTliN2Q1ODJkMzlkZDVlNTNhNDVlYzgyMDQ5MmVjMTIyZjFiMmFjOTMwNWJlMzBiMjljNTQifQ" + }, + { + "resource": "repo://src/session/mod.rs#L226-L264", + "version": "repo-lines-v1:sha256:fcc6b7fb2029e2717576e2143195e6d3714fe666673990f26d169708d40cc736:eyJzZWxlY3RlZExpbmVDb3VudCI6MzksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI2YzRhODMzODY0MTM0ZDcwNGE4M2I4NGNhMDkwOGNmNjE0ZjNlNGI4OTU0ZDM0ODlhNzQ0ODkyYmQxZmJhNTYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImY4Y2JkNTg4YTdjYzNhM2Y0NmY2ODkyYjExNjk0OTMxMmUxYWVkOWY5ZjU4Njc1ZmE5YzFkM2NmZWMzMDgxNGQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImU5ZDgzNjUxMWI1YmZhMWZkZjc3NmY3NDA4MTYzNjhhMTIzOTViNjIyNTNiMWI1OTczMWE0NmQ0Yjg5YWFjZTIifQ" + }, + { + "resource": "repo://src/tunnel/spawn.rs#L128-L141", + "version": "repo-lines-v1:sha256:1a52a25dba915bb641c366a7caf9e2c89ffdf266ce0212c69844003c39c0b0d6:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImM5ZDczYTUzNDhkMTZiZGU3ODMxMGUyMzI2MTc1M2U3MzhkYWYzOGY5YzIxN2Y0NGE4MmY5NWQwOWYwMDg0MGMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjI1OTI5Yjc3NWY5MjE0OWJlYmM3ZWEwOTgyYjU5N2I4YzJhNGFmYzIxMGZhZmI0YzgxOThiNGEzMjhjZDljYjkifQ" + } + ] + }, + { + "id": "claim_a6c7907298d64a5c8b0fb81197a124a7", + "statement": "`ssh-keygen` passphrase handling (`src/ssh/keyfile.rs`) stages secrets through its own KeygenAskpass helper — a 0600 secret file plus a 0700 helper script exported as SSH_ASKPASS, both removed on drop — explicitly to keep the passphrase out of ssh-keygen's argv where `ps` would expose it.", + "evidence": [ + { + "resource": "repo://src/ssh/keyfile.rs#L11-L77", + "version": "repo-lines-v1:sha256:4a177df8eaf8eb8421a5044b2c2b7b17c361d90f3f228a048e57be974f271e3d:eyJzZWxlY3RlZExpbmVDb3VudCI6NjcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNjNjQyMGZmM2U4NzU0NDQyMjBjNmM5ZmM3MjIzOWZkYzg0YjEzZmFkOTQxODIyZDcwYzQ5YzQyNzRlNTFlODkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjIxZTJlM2I4NjU3MGJjM2JhOGQ3YTIwYWQ0NjUyM2JmMjVlNmYyMjAwYmU1NTZhOWJkNjA1ZjNmMTg1MzViZTQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImU5YjkyNDZhNWIxMmFlZmMyOTcyNjkyNjg4Y2I1NDcxNWJkZmUwY2VkNjk3MGFhNzVjYjg4MGQzNGQ0ZWVhNDcifQ" + } + ] + }, + { + "id": "claim_2ad66c2964d3419a910f11ab9d9a1697", + "statement": "0.14.2 option-injection guard: `safe_ssh_target` (src/ssh/host.rs) rewrites a leading-dash connection target into the `ssh://` URI form OpenSSH refuses outright instead of parsing as a flag, protecting managed/direct connects, alias connects, mosh alias connects, and tunnel spawns; ordinary targets pass through unchanged. A differential test drives the real `ssh -G` and asserts the unguarded form still resolves `proxycommand id` while the guarded one is refused; `ssh -- ` is not an alternative because words after `--` become the remote command.", + "evidence": [ + { + "resource": "repo://src/ssh/host.rs#L31-L86", + "version": "repo-lines-v1:sha256:06726184803812c7a5ccbe3519c5e90eaa3a784a2dd0cfbf707d1e1d4f119db5:eyJzZWxlY3RlZExpbmVDb3VudCI6NTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNiMDUwOTdjYWQyZTJhYzI2MDE4Y2NmM2I0YjRkNWI2Yzk3NDI1MWZjMDZjMGU3MjZjYzNmZGVmZDFlMDhjOTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjA2NjJjOGE4MDZjMjMzYzRkMTM3MjQyYmE0NDBkMmU1OTZmYzRkYWFlYWMxOGQ4M2ExZGZmOWUxMjM4YTI2OTYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhkOGVlZTU3YmRhNmExM2ZlMGFjYzNkNGM4MDE1MzlkOGVjMGFhMTk4ZDM5M2MwYmI5NDU0ZjQ0YWUwM2Q1YjUifQ" + }, + { + "resource": "repo://src/ssh/host.rs#L336-L412", + "version": "repo-lines-v1:sha256:59852c4ef86bf6e857608a1acc48ee14dee3a0c20afeeb3f40bf71b6f9a93486:eyJzZWxlY3RlZExpbmVDb3VudCI6NzcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjZhMGY4NzRkZTA3YmNiODViOWE3NjUxNjZiMTJiN2QzNzQ5M2Q4YjdjMzQ3ODE2MGM1ZTc1Y2NiNDBhZGNiNzIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImYyZDgyOGEwMzdhYzFjODE1MzViMWQ1NTAwODMyMzIzYmZiYjg2NmEzYTNmODdlN2E2OGMxM2EyYmFlNTZmZmYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEifQ" + }, + { + "resource": "repo://src/ssh/host.rs#L88-L107", + "version": "repo-lines-v1:sha256:0855157bae5856cb010151d37ec33abc461233c80742b5d9cd9c0057cf0028b9:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRhZDMwNDVlODI0NDA2MmYyMzYwYTU3MzdmMTZmNDkxYmNmNDJlMjJkOTg5MWIyYTk1YmRhNTJlNTUxZWVkMGMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk3M2ZhMTMzYzY3NTU0MzE2NzIyZGI3YWNmNDI2NDAzNGNmZGRhMGQ5ZWNkZThhNWM0MjY3NjVkOTQxODA5YzciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImM1ODc1ZDMzNmI5NmVlMGNkZjdlNmJmYjg4YmU0NDRhYWQzY2MwMGZkZjg1Yjg3MmZiYjRkNTA2NTAyZTMyZWEifQ" + }, + { + "resource": "repo://src/tunnel/spawn.rs#L78-L92", + "version": "repo-lines-v1:sha256:f421e81520a62f58e0ed8f80be925d60e62c68b0c76d3ffadeff64ad9f5321ee:eyJzZWxlY3RlZExpbmVDb3VudCI6MTUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjlhN2E3MWEzM2FmMDEyZTIwYjMyOWI1ZGFmNDYxM2UxNWZlYzljZTM4NTVmNGY5ZjlmZmEwYzY5MGU5NGU0ZjYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjEzOTYzMjI5YjQ0ZDFhYzkyZTg0YTczMWM3OTc4YzUyNWVmODFjZWUyMjkyYzY0ZjY5NWQxNGJlNTU0ZGIxM2MiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImIxNTFjMTkwNzQ0YzZmZWQ4MzRiZDhjOGE5YjVhYjhkMzE5YmNkZTA3Mzg3YzUyZWY0MzU5NWY5NjlhZDJhN2EiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImZjYjUwNjE3MjQ2MmJkZWYyNDE0Zjg3YjFhMmJkZDgzZDdlOGVhMDY0ZjdjYzM4YjNhMDIyZTkyN2U3Nzk3MjYifQ" + } + ] + }, + { + "id": "claim_7b50941656fd466196e42420add10494", + "statement": "Write-time injection guard: `create_host` and `update_host` call `reject_option_like` on `name`, `address`, and `username`, refusing any value starting with '-' ('ssh reads it as an option rather than a host — refusing to store it'), so a poisoned row never reaches the database; `is_option_like` is re-exported from src/store for the TUI host form, which flags the value with the form still open instead of unwinding a store error through a key handler.", + "evidence": [ + { + "resource": "repo://src/store/hosts.rs#L1481-L1540", + "version": "repo-lines-v1:sha256:104f8a06d9a5a4604d5e27d0cef85871e180ebcef481b883af4902757fb03369:eyJzZWxlY3RlZExpbmVDb3VudCI6NjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImI1YmU0YmQzNjExNjFlMjA3NWE2OGFjODg2NWIyZTVkMTVmNThhNWNkZjA5YzgzMmVlOWYwMjhiYzFlNGNkM2YiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImYzMmIwYmQ3YTcxYzg3MThiZWFkZmE5NjAyZmQ5MDcwMWE2MDY2ZWUxNGM5MjkzYTNiNGJkY2UzZGRjZWY3YzEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjYyN2JjNzA0YmYyZmY5NTZkODg1ZWRkM2U1NDc0OTQyOTdkYjU0NjM3OTMzNmQ1NGMwNTU1ZGE1ZmE1NzI0NGYifQ" + }, + { + "resource": "repo://src/store/hosts.rs#L204-L209", + "version": "repo-lines-v1:sha256:6ecb6543212b8103e776a20aa5aab2db644e93dfb1051ab712c13ad27b61a065:eyJzZWxlY3RlZExpbmVDb3VudCI6NiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYmYyNTU3MTUzZGFmYWVlNThmYzMxNTgwZTViODdjZmZmMzgxZjY0MTliM2E0NmMwMGZjMjA5YmE3MWExZmE0NiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZTZiMDQ0OTE0Njk3MjJlODM2M2E5NGU0MDQ0NTM4ZTAxM2EyYTIxN2UzNmIxZTU2OGJhNjBhY2I3YjRjOGYyZSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMWYyNDA4YTU3YWJkMTJhY2VjZDA0OGRjNzBlNWI2YjYwZWYxMjRjNjcxOGIzMzhmNzk2M2E0MjI4OWYxMWJjNCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNjEyMmFhZGQ3NGZkZDgxNWIyMjEyZWQ5OGZhMTMzMjNlY2YyN2EyYWFjM2QyOTQwN2RmNDZhNDZjZjEzNWRkNyJ9" + }, + { + "resource": "repo://src/store/hosts.rs#L311-L320", + "version": "repo-lines-v1:sha256:c362f51dcb9e77a48f77e694df2579e2ced45ca569f4653185f1aaf4b3264da6:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk2NTI2MjBjYjcyZWFmM2IyY2E4NzcwMTRmYTdmZjNjOTQ3ZTAwYmNjYjVlNzY1ODg3MTZmN2JhMTgzMjZkOTQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImViOWEwOGJjMjlmNmViZWJlOWE1ZjAxOTAzZDk4NDVkYmNiNTg0MmE0NTVmMDdkNWY0MDAyZGFjMzJhNWM2MDUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhlZTRkNDYzMGU5MTBmNTE3Yjg1NjQ0MmYxYmEwZWQ1NTZjMDM3MDA3NmRmNWU3MGU1OTIwMGUyYzA0YTg3NjAifQ" + }, + { + "resource": "repo://src/store/hosts.rs#L887-L910", + "version": "repo-lines-v1:sha256:2c9942e77b2b2c22ec28529cd519206744a9585b8236d598359faf88787a4eed:eyJzZWxlY3RlZExpbmVDb3VudCI6MjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjYwOTNiMTNkOWQ0ODNhZjFiNGRlNDIwN2QyYjYzMjg0Y2ZjODE3MDRlMjk4Njg2MWM3MWRkOWFmNDg1YWY0YWQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImEwYTEyNjk5YzE4MDk4MDg0Mjk3OTNhOTNkZGU2MDcwYTU4Y2VlMzBmZmIxMmQ4MTEyY2FjNjI0MmU0YTI3NjEifQ" + }, + { + "resource": "repo://src/store/mod.rs#L9-L9", + "version": "repo-lines-v1:sha256:47fcc13e244ea2fa5fce4afbce44c6e2ee65f23832ed7f449ea647ea57e7ac0e:eyJzZWxlY3RlZExpbmVDb3VudCI6MSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNDdmY2MxM2UyNDRlYTJmYTVmY2U0YWZiY2U0NGM2ZTJlZTY1ZjIzODMyZWQ3ZjQ0OWVhNjQ3ZWE1N2U3YWMwZSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDdmY2MxM2UyNDRlYTJmYTVmY2U0YWZiY2U0NGM2ZTJlZTY1ZjIzODMyZWQ3ZjQ0OWVhNjQ3ZWE1N2U3YWMwZSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNzZjY2MyODMyMmI3ZjU3YjQ1MzI5NDNhMTNhYzI2NWVhMTFkZmRhZjIxYjRjZjM0ZmE2NTE0MmY1MjM2ZjU4NSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMjllZmJhOWM1ZDIzZDQ0ZTA0ZjQwZTk3M2I2ZjkxYzQ4MTgzY2FhYmI5OGVlZjUzM2JkZmUyNTEzMDBmMDEwOCJ9" + } + ] + }, + { + "id": "claim_1fbd889bd2fd4d57b6e2a2c3353307a3", + "statement": "Every importer (PuTTY, mRemoteNG, Termius CSV) treats one refused host as one dropped row: the poisoned entry is skipped with a diagnostic naming the host, the rest of the file still imports, and the refusals are counted as `skipped_invalid` in the import report; the CLI summary prints 'N skipped (refused: a field ssh would read as an option)'.", + "evidence": [ + { + "resource": "repo://src/cli/inventory.rs#L219-L244", + "version": "repo-lines-v1:sha256:c75db93a9a8b50611044c18a195dcb25a8bbb17077f541fd2c0e3ab7c7b89260:eyJzZWxlY3RlZExpbmVDb3VudCI6MjYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM0ODkyNDA1YmVkZGIxZTAzNjI2Y2I2Y2VmYmE3M2JlNTBkMDcwZGU5ZWVkOGEwMDc0OWNiNDA2NGUyMzdiMTQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImZlNmZkODcyZGVkOTAxODhlZjk5NWViYmMwN2RjNDM0NDA2ZjliZDE5MDZmZGYzMTQ3OTNiNDNhNjZiNjMyYTIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjMyY2U4YjI0ZTJkYWJhZDc3NzUyYTZjY2M2Mjg1ZGNlZWI2NTBjZDEwMjVjNTI2MWMyYzkwNGViM2JlNzQ2YTIifQ" + }, + { + "resource": "repo://src/import/mod.rs#L16-L19", + "version": "repo-lines-v1:sha256:3b95073fe2ab03d569483044b7438ec5bb3967c7cd42c5f59b0ec3215d63dbec:eyJzZWxlY3RlZExpbmVDb3VudCI6NCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYzNmY2E0ZWY0MmFiNGQ5M2ZjNTZjZDA0YTRjZjM5NTEzMmNkNzI1MGY0MjAyODBjNTVlNGIyYjRiMzk3NDZjYyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNzdiZDNhYTU0MGEzZDJhYmE3ODg0YjkxNDQxNDhiZDZjNmYzMDQ1N2E3Mjk2OWE1M2RlNjM5MWU1MGNlNjgyZiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZjkzZDJmZGNhYTQyYmY5OWZlZDExOGIxMjAzZDM4YWVlOTI4NDFlMTMyYTk5NjEwMzU2YjRiNmJjNmI1YmE1OSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjEsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSJ9" + }, + { + "resource": "repo://src/import/mremoteng.rs#L449-L457", + "version": "repo-lines-v1:sha256:d4eabe1a0518c2c9ecbb692df8e84b968fb2a0dc8de9c508a8a03841a344a3b0:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMGU1ZTc4OGMxZjExMTlhZmNmMTQyOGI3ZTkwNGYyM2ZjMTM4ODNkNmY0YjM5MjZmMDM2YTNlMjdhOGY0NDU1YiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZTZiMDQ0OTE0Njk3MjJlODM2M2E5NGU0MDQ0NTM4ZTAxM2EyYTIxN2UzNmIxZTU2OGJhNjBhY2I3YjRjOGYyZSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMjM2YzQwOGZmZmY1N2RjMDFkZjY5Mjg0ZTZmYTczZWNmMDMwNDY4YmZhNjMwNTc0YWEwYmNmNzMwODk5ZjIzYiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiY2JjZjFjMjBkNGRkNWNmZDJjZDQxYTVlYmZlNjU0OTUzNjRiN2M0ZjE4MDcxYTc2MTgxZWRmOWRkMDQ3YmYzZCJ9" + }, + { + "resource": "repo://src/import/putty.rs#L392-L399", + "version": "repo-lines-v1:sha256:9d767c55d9d4f5dcf8c381a15f9102861c878a81e586919e65146d2000cc3cc5:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZDllZjdjNDYyMDUwNDc5ZDc4YmUxZjJiYWE4Mjg4MDMxMmU3ZGZlMGIwYjQ3Y2ExMjM0ZTFiMjljMGM1NjQ0NSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZTZiMDQ0OTE0Njk3MjJlODM2M2E5NGU0MDQ0NTM4ZTAxM2EyYTIxN2UzNmIxZTU2OGJhNjBhY2I3YjRjOGYyZSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiY2QzZjZhYzc4YWQ0YTM5MWE2MjgyNDIyNTNmN2NhNzk5MDJkMjYzODQ3NTY1NmUzYmJhOTFhYzAxMGU2NzFmNiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiY2JjZjFjMjBkNGRkNWNmZDJjZDQxYTVlYmZlNjU0OTUzNjRiN2M0ZjE4MDcxYTc2MTgxZWRmOWRkMDQ3YmYzZCJ9" + }, + { + "resource": "repo://src/import/termius_csv.rs#L327-L339", + "version": "repo-lines-v1:sha256:64a7c25d96c9ab3c2e6894dd305c001a455903eaabbe3604649a6a2fd77ae1be:eyJzZWxlY3RlZExpbmVDb3VudCI6MTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBmNjQ2NzNjNmU2N2UwNTliYjRkODgzOThhZTk0YzM4YTgyMDA4MTUyYzQyODU2YWYxNTYxOWU3NzVjMmUwNTgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU1MjJjNDJjZDE5YzZkMDNkODA4YzQ1MjcyMDU1MjgyNWI3ZWExYzViNDkxYjIxODUxMWNhYzc0MTVjZjlkOWIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjUwNzQyZjNjYWU5Y2I0ZTM4NGMyNjI5OWZiNWVjZjdmZGZlNDY5NDExNmZlMDYyZDA0OWRlODE4ZDkyZTdjZTEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQyMmJhZjMzYWFhMDdjNTJmYzRhYmU4YzNiMzA5YzI5OWY0NzIxMjE4NzhkZjFlN2IwODk1ZmE2NTU5NGJmNTkifQ" + } + ] + }, + { + "id": "claim_d0fed99d55224bbd8ab3468a42c0e6cb", + "statement": "OSC 52 clipboard relay lets only the currently visible embedded session write to the host clipboard, and only while `[clipboard] relay_from_pty` is on (default true, configurable to false); empty payloads are ignored (a remote cannot clear the clipboard), clipboard reads are never answered, background/non-session views drop writes immediately, payloads are capped at 64 KiB with a bounded queue of 8, and drops are counted by reason and surfaced. Framing and selector normalization (primary `p` → clipboard `c`) live once in src/osc52.rs. Because the sequence travels in the raw PTY stream, enabled session logs may contain it; the relay never adds payloads to notices, diagnostics or logs.", + "evidence": [ + { + "resource": "repo://src/app/session.rs#L247-L285", + "version": "repo-lines-v1:sha256:9f1bb338b9e280cd24ebeaaa95b6a700ae9cc8de0ffce117274f401549831fcd:eyJzZWxlY3RlZExpbmVDb3VudCI6MzksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjNmMTUzNDhjMjJiZGJiNjVkODIwZjhkZDMwODg2M2JiZTAxYzdhMDJlMTE0NmNkNjYyYWJmOTBmMjRiNDRjYmMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImJjZWZhMzk5OGQ3NDA1MTc5ZGUwNjZkNmI5MmY1NGEyNzUyNGNhODNhYTczMDM2NTMxY2NlNjNkYjdjMjIyYTIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjI1NzY2ZjAzZDkyMTc1OTczNjNiNmJiMzk2NmRmYWM5OTUyZjNhOGM4ODBiZjYyZTRjNGQ4MmY0MTIwZGIxMTgifQ" + }, + { + "resource": "repo://src/config.rs#L99-L116", + "version": "repo-lines-v1:sha256:681d981d38ab0be744bee86bcbf52e23ae62f6ac41cd90b7902cc25b8b2436ad:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjljZDhjZDQ3M2EwZThhNTI5MjUxNmJlMGE4Y2NmY2JkZWM0ZGNkNWRlZmU0NmI3ZTgzMTBmMjUxMjliNzUyMTQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImEwZGMxZThlMzkxNjc4YTk5Mjc2ZjVmMGQyOTE4OWViMjYwNGUyZmNkNjY0NTQwZjgyYzJiYzM1NjI3NGZmYzgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImYwMGExZTY4MzJkZjQ2MWNiM2Q5Yzc0MzA3YjQ2M2FlMTA1Nzc3YmVmYjExOWNiZTA0NTY0NjQxNjY5NTAxYTIifQ" + }, + { + "resource": "repo://src/osc52.rs#L10-L26", + "version": "repo-lines-v1:sha256:ea065b81294e7c484d5e563a663938f4d25347e1f73563278e8e592ea17b1e45:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjIwMjFmYjc1ZTY5YzVkYjI3YmJjMjI0ZDZiYzNhNTBjZTNlNWI1ZWNlNjM2ZTIzZjM1MTU2NjA2N2NiNDg0ZDAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijg5NDNhZTFhZGZjNmZhYzg1Mzk4MjdhNTBkMjA2NTM2MzgzZDUxZWQyNTgxMWIzZGQ1ZWQ3ZmQyMGUwOTgyODgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjdmY2UwMzY1NDA0ZGZhMDY1MTdlYTM4NDg3MDI3NDQ3MzBkZTQ5OWY1ZDJhMTEyNzZmMDIxYTY5Y2FiM2EwZjQifQ" + }, + { + "resource": "repo://src/session/askpass.rs#L1-L9", + "version": "repo-lines-v1:sha256:271af3dd83d9b09364c5e1e5dc18b85d0bac654b79fd7f9cdaff070acc506429:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMTBhYTczMmU4NDU4NTAwMmE0MjhhZWIyYzdhNzcwODNlMmFiMWRhMjkzMmVlMGMxZGI2OGZiZThiMTc4OGNkOSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZWI5YjY1YjkzZTM1M2IzODNhYTY3MmQyMzM5MWM5ODc2YWRiN2QzNTYyNzQxNDFjODc2ZTg3Yjc4ZjgwYjg3OSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjAsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZTNiMGM0NDI5OGZjMWMxNDlhZmJmNGM4OTk2ZmI5MjQyN2FlNDFlNDY0OWI5MzRjYTQ5NTk5MWI3ODUyYjg1NSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMWYyZDBjNTFmNzkzMGRhODFiMmEzMDUyZjBjNzMwZWJjOWZkZTcxNTYxN2ZiNTY4OGI1ODMxYWY5NjRiOTU4NyJ9" + }, + { + "resource": "repo://src/session/mod.rs#L629-L680", + "version": "repo-lines-v1:sha256:25b91f8e3862ec5d7dd01577915dce05816e19f14bbaedb501bccd2d54eb31e2:eyJzZWxlY3RlZExpbmVDb3VudCI6NTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjVmZjQwMGM0YjFhMTgyNmVmYjFmMjllMjAwMTU1MGFmODk0NDI3NTY5NzUxZWJlNjIxMjM5NDFkOTY1YmVhNzYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjVhODhlMWU4ZWMzZDE4ZjFhYTdhZDAyMWZhYmY0NzFjYWFjNTAyZTVmZjQxZmNlOTM2NTU1NGM2OTliYTllOWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImY4ZmVmOWJmNGY2ZTEzYWMyNGE2ZDEyNTRiMjViZGFjMTczMGNlMmIzZmNhMGYwMjg5NzNjNjAyNmJiNzJhYzEifQ" + }, + { + "resource": "repo://src/session/parser.rs#L7-L95", + "version": "repo-lines-v1:sha256:68bef044a00df24d2be78e93e565ba7fc56c39e58b0a0db57f262c8326ba5e39:eyJzZWxlY3RlZExpbmVDb3VudCI6ODksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU3OWUxNDkzYjAzMTE4YmVhOGExYTZlMmZjYmY0NTcyNGFiODZlYWFkZTM5OWFlMGY2NWUwMjYxMWM2NmI3NTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQwNjczYmNlMDE4MTdkZjM2MjNkZTcwYmIwNmVlNmJhMDllYzNlMjg2YWM2MjgxYmE1NWU1YzM4NmQ1ZmQ3ZDEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjljZTkzMzhkOTFiMTgxZjNhNjg1ZmQ1MjY3NDA5ZWYxMzUxYjhkMTBmNjY5MjNhYjcwNGNmNTA2NWEzZDM4ODgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImZkYTQ1ZmU4YWE4ODc0NGEyMzFiOWMyY2I4YWYyOGFhN2Y1ZDlkZTQ1NzE5ZGM2ZDk0MjQzZWI0Y2Q3NTc4NGUifQ" + } + ] + }, + { + "id": "claim_f5687b94f3af439fa75aadd350781b37", + "statement": "Host-key policy is TOFU mirroring accept-new: ssh spawns inject `-o StrictHostKeyChecking=accept-new` when a secret is staged — embedded sessions and `sshub connect` (prepare_session_connect_argv/prepare_cli_connect_argv), tunnel spawns (with BatchMode=no), the inner ssh of a mosh argv, and public-key push — because with SSH_ASKPASS_REQUIRE=force an unanswered host-key confirmation would otherwise deadlock (the askpass helper would return the password instead of 'yes'). Changed keys are still refused; the session detects a changed key in the `-v` log and offers opt-in purge of the stale known_hosts entry ([addr]:port for non-default ports, plain addr otherwise).", + "evidence": [ + { + "resource": "repo://src/app/connect.rs#L25-L38", + "version": "repo-lines-v1:sha256:6973fbe3a7227a4195bb6fdaf4d38f7e3e6d06f9426db0bfa0c2358f6e1246c3:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImIwMWJkYWYwOTA1ZDFiNjE2NWUxNjc5MWNhMzkxMTlmMWI1YTdlZTc4MjJhNjAyZTcxMTcxOGQ0MjkzYzlhYzkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFiYjEzNjAxYTU1YjlhMThmY2EwYTRkNjAyMTZiZjhlYmE2YmIwODg2NGJmYjkzNWZiNmRlMTNkNmZmZmRmZWUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImVhNzAzZDMzYzc2NjQ1YWYzOWZiNTQ2Yjc1ZTNjYzEyMWQyZjc0YWVhYjYyOTMzYWNhZjNmMDE5YmNkZTZhYzQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImU3Y2VkNjU2OGM4ZTk2YjM5NWU5NTQ5MzE5NWMwMjRlMzI4YzljNjI1NGQ1NzRiNzRmNGFmYzAxOWRmZWQwZWUifQ" + }, + { + "resource": "repo://src/app/push_key.rs#L240-L247", + "version": "repo-lines-v1:sha256:73f820a0d690ad72ef06b664e3d217fc736bc1b7276ef7c02d8be3fd751d60dc:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNGNmYTcwYzBlYjZkZDQ5MjRiMDQyMTE3MTkzNzA1MjQ5NWFmMzFmY2I2YjIzMmQ0ZmZkMDRiZmU1NTdmNDNmMyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZTZiMDQ0OTE0Njk3MjJlODM2M2E5NGU0MDQ0NTM4ZTAxM2EyYTIxN2UzNmIxZTU2OGJhNjBhY2I3YjRjOGYyZSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiY2ZlY2E1ZTgzNTcyZWQ2ZGU1NzVjOTQ3M2MxOTQ0NDNkZTZkNGMxMzQ5NDk3NmQ0MTQzNjM3M2NhNGNjMzcyMyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMDQ4YmVmNjdlMTVkZTM0YjFjOGFhMmNmYmU2ZGRlMjAwNmI3ZWZhMTQ1ZTM2NWNjNzA5M2E0MmY4MTAxNTEzNyJ9" + }, + { + "resource": "repo://src/app/util.rs#L183-L220", + "version": "repo-lines-v1:sha256:840cee9cff6ea56f0a324ce642b41e52465187a76f8a0073909b6431759c3745:eyJzZWxlY3RlZExpbmVDb3VudCI6MzgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjcwZmEyYzQ3M2FkYzFlZTMyODYwNzEwZDI0OWNlMTU1YzQ5N2IwYzc5Y2QwZDAxMTNlMGY3ODk2MTRlODY0NzMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjZkNDI5YWM4ZjQ2ZTQ4ZjYwZjZhMjAxN2E3NGZmMTMzNzMyZWRhZGZkMGVhZjlmNjk1OWU3YTllMDJmNmVlZjEifQ" + }, + { + "resource": "repo://src/ssh/host.rs#L119-L141", + "version": "repo-lines-v1:sha256:76bf39b0607d1908aa8eb52c6bb20eca84602c45915e4b1cbf8c9dd4b7fbf723:eyJzZWxlY3RlZExpbmVDb3VudCI6MjMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA0Y2E2Y2E4NjM3ODFmNzQ2ODZiNTY5OWY5N2Y5NDU5OTNmMjMzMmVjNWU0MDIyZGI0MjRkMzA0ZTI5MWEyZTciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImM0MjkxYzkyNjNmN2U0YzUwNDRlNDI3MjMxZmQxOTE4OTRiZTU2NjkyZTM0ZjAzMTEzYjc5YjhjNjFhYzRjOTYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjM2MWE1ZGY5MDg0ODRiYmQxMzhlMmYyZDA4OTYzYTgzMmIzZDFmZmRlM2FlYjMwN2Y4ZGNmYWI0ZDc3N2FhM2YifQ" + }, + { + "resource": "repo://src/tunnel/spawn.rs#L95-L126", + "version": "repo-lines-v1:sha256:7dad58b4e361b44287991492ed0c1ef2c022017c0a03dce203334f250a30408a:eyJzZWxlY3RlZExpbmVDb3VudCI6MzIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjYwMjM5YTk3OTliZDdkYmQ5YjA3YmU0ZWVlNzFkNWMxNGI0Mzc0NjNhYWU5NzliOTljNmQzMWNjNTk0YmExYjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjYxOTI5ODQyNGM3OWZiZTZmNDUyYjdjNzQ2OWI4ZDI2NDMzYTFjZmNlYmY4NDk0NmEwNGIyMDA4MTMxMzIyNDEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjM2YWMzMGZhNGU3ZTk4MWY3MTI5MzZmM2M5YjZmOWJjYzExYjU1MmZlYTEzZThlOGVmYzczN2JlYTc3NmIxNzQifQ" + } + ] + }, + { + "id": "claim_857a1f85fa904714a30a996223853abd", + "statement": "SFTP mirrors the TOFU policy over libssh2: an unknown host key is recorded and accepted by appending the entry to ~/.ssh/known_hosts manually (libssh2's write_file rewrites the whole file and would silently drop unparsable lines such as @cert-authority, @revoked, and certificate entries), while a changed key is a hard 'possible MITM' error; a missing or empty known_hosts file is treated as 'every host not found'.", + "evidence": [ + { + "resource": "repo://src/sftp/transport.rs#L123-L184", + "version": "repo-lines-v1:sha256:abf4506da33658d7c58bd0b7ad92bb47e49cc01ed6db71788d11cc5f4645658e:eyJzZWxlY3RlZExpbmVDb3VudCI6NjIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImI5YTc0MTg5NjhhMDk5MzNjMDk3N2ZkNmI3ODYyYTA1ZGQ5MzU3YmE1NWYwMDEzZjJhYzQ0NTE1MTExOGNjMzQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjIwMWYxNjQ0YzVlOGI3MGJhZGFkNTI1Y2QyZTI5Mzc1NjYxYzVkM2ZkMDljZjFiZDgyMDk2ODAzZTk5NDg5YmUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjMyNjg4MjdlMmM3MGU4MDc4YTYzZDkwNWU0ZTVmNWZhZDNjMDczOTBmZTU4ZWFjOGQ2ZTA0OGIwMTQ5N2VkZDQifQ" + } + ] + }, + { + "id": "claim_c8365d21b4074b15b66dea0d1dc38dd7", + "statement": "Session logging is opt-in but captures everything echoed to the terminal, including typed passwords; the in-app help screen carries the warning ('captures all PTY output including secrets echoed on screen'), and external launches wrap in script(1) only when logging applies, warning and continuing without a transcript if script is unavailable.", + "evidence": [ + { + "resource": "repo://src/cli/host.rs#L173-L198", + "version": "repo-lines-v1:sha256:cfb4fe622f4d7e6a6db52a31aae512c0509e431407dc46540c398779c122838b:eyJzZWxlY3RlZExpbmVDb3VudCI6MjYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImJjMDE1ZGQ5NmRiYmEwZmRkOGNkYmJkYmYwZWFlOTFhOGRhYWM1NTZlMThmNGQ5NmQ1NzZiODM1MzUwNzQ5MzgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjUyOTAxM2NmMTQwNmEwMzljMWM4NmIzZTZlZDMxYzIzZjIxMjgyY2YwOGY1NGQ2MjllZjI3MjE1YmY4Y2IyOTciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI4YTliNDhhZDE1MGMwMzhkYTViOTYzZDczM2YzN2Y5YjEyYmMwZTZiMzU2ZTU3OWRlNmE0NzlkYzlhMGY3ZGQifQ" + }, + { + "resource": "repo://src/lib.rs#L331-L336", + "version": "repo-lines-v1:sha256:dfd9540200ae8d9a7fe8cba44ba64b0becc2d4aadf936b6310a1425182061a06:eyJzZWxlY3RlZExpbmVDb3VudCI6NiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZWIyOGNmZjhmMzE4YmFjZjcwYzQ3MzgyMGY1YjA0N2JmOTQ0NTJhZTllNDdjNjJiNGY2NzkzZmViNThlODU1OCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiN2FhZTE1Y2VjYWU4NjIyNGVjMGIxZGRhNjhjYjg0NGE2OGIxNjQyZDBiOGQ1M2M5NmI2MmMwM2ZmZjMzY2E0NSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZGE5Zjg2ZDI4ZTc4NGY5ZmQzMWZiNzJkMTVmNjBjYmNlYWFmZGQ2NjljYmQ1YjZkYTc4ODhhMDIzMzIxZWJiOSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZjc2Mzg2MTRkZWY1YjExYjQ3ZDMxODEzYThmMmZhYjdjMzU4NmZhZmRlNGI2MWQ2NjkxZTk0OWEyYzE1NDAxZiJ9" + }, + { + "resource": "repo://src/tui/screens/help.rs#L446-L449", + "version": "repo-lines-v1:sha256:ede4bdb0f10d4eaa0918b09399319e157a3d9bf0d3dfd55a06a9c2384cdbd16c:eyJzZWxlY3RlZExpbmVDb3VudCI6NCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZDM5M2E1NDRhMjgxMjMxNWUzZTQxZDk1MTNiYjZlOWQxM2MwYTdjZjE1M2ZjODRmZjBjOGU1MjIzZDZmYjI4YSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMWYwY2RiOTcyYTdiZmI3ZWQyNDZmOWZmNjcyMzg1ZTU1ZjQ5NjhmYzExMDQzZGM1YjY4NGQ1MWRmYTMxZGJjYiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYTdlNjI4M2IyN2U3Njc5MTJkYmRkNDQ3ZWRhOGY5ZDNlZTU5YTdjMmVmMTQxOGJlNTIxYTBiNzY5YjZmOTM0MCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiOTVhMTI2ZjBmZTBhNTBjN2VmMjJhMmQ1NDU3MGM1YzY3MGY5YzFhNDZlYjZkZmY4ZWFiYjE5MGRjMmRlNWU1YyJ9" + } + ] + }, + { + "id": "claim_16bbe0cfeeac4968a8cbcdf1f57c5182", + "statement": "`secure_fs` (src/secure_fs.rs) is a best-effort, Unix-only hardening seam: restrict_dir sets 0700 and restrict_file sets 0600, never failing the caller on a chmod error and compiling to no-ops off Unix. It is applied to data/log/PID directories and secret-bearing files: launcher/metadata databases and their directories (covering SQLite sidecars), credentials.json, session logs, askpass staging, tunnel PID files, completion caches, exported config directories, and profile migration staging.", + "evidence": [ + { + "resource": "repo://src/credentials.rs#L133-L176", + "version": "repo-lines-v1:sha256:945a51059a0d1ff75da428107183e9cb76a060bedce16e5650620412fd3bcbea:eyJzZWxlY3RlZExpbmVDb3VudCI6NDQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImM3YmEzMzZjMTY5MWY0N2RmZGFmODg1NTViMjA1ZmQwN2Q2N2VkNmNlMWE2YzhiMzQyOTZlMmU1ZGI4ZmJjMzIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImU0ZTczNzM4MjIxZGIzNTAwM2MyNGNiMGI3MTFjMjlmYmM2OGQ0ZjliYmUzMWY2ODVlZDQ0NjBiYmRjNjg0ODYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImM0OWNhMWY1MjVjYjI1MDJiZWZiYjY0MDQzMjFmNDA2NGJlNjMzN2I0Y2UxYzc1YmM1ZjA1MjI5MzFmYWZmMTcifQ" + }, + { + "resource": "repo://src/profile/migrate.rs#L77-L165", + "version": "repo-lines-v1:sha256:11d66b4dec4113eb68609cf42d010b747538f1f5597af351109d5cecd14b8f2d:eyJzZWxlY3RlZExpbmVDb3VudCI6ODksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImEwZDY1ZDk4MjcyMTM3NmNjMDgyZmZkYTE2ZjQxYWJjYjI1MmI5MzRjZTczYTIwNDNjNzc0NjY3ZWQ0MDc2ZGQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjZlZTFkYzljMDBhN2YxZmExYjk1NzNhNmIwZDY1Zjk4ZWMzMjljOGM2ODE5NDZiOGQzNjI2ZDg1NjI5NDE4YzEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjdiN2FkMzI2ZDI4ZjMwOTcyMTBmYTA0NmIwZmZiN2Q2OTUyNDRlNzRjYzRlZGE0ODJmYjZiZmI0Mjg0MGYzNTAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUyM2NmYWJkMDRkODQ0Y2UxOTEwNTQ2NzZjOTY5YTc4NmVlMTM1NDBhMWYzZjg0NDhkY2FkNzkwNDg5NDVmOGEifQ" + }, + { + "resource": "repo://src/secure_fs.rs#L1-L33", + "version": "repo-lines-v1:sha256:2649a3291f57c0bc2bffc0b6b263cba498a187030a8d9e83c89612cdec9fc67d:eyJzZWxlY3RlZExpbmVDb3VudCI6MzMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImY1YTdiMzQyNmE4NGMzNzhiMDQyZjg2M2RkN2IzOGY3ZmEwZWFhYjAxY2ZjZjk4NWM0NDc4NmNiM2QyOGI3Y2QiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijc0MGFlZGEwYzI0OTM1NjAxODE0MWQ1NTA3NjE4NzdjZDFkYTkwZmM2NDI5YmRhMTQ4MTRlYWZmYzMyNDNkMzkiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjI1YTFkZjZjNGViMTNjMzExNDUxYzFhYmNhYzM4OWJlZGY3YTUxYThmYTlhMDUyNmEzMWE5YTkxYmU4N2NhYzAifQ" + }, + { + "resource": "repo://src/session_log.rs#L125-L176", + "version": "repo-lines-v1:sha256:d792c5ac9d19a19bd611ab391327023049275f47c7edf17105790e774582b66f:eyJzZWxlY3RlZExpbmVDb3VudCI6NTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjZkYzlmODNjZjRhZTYzNTYwM2I1MjM3MzA2ZDIyOTVkMTg3YTlkYzc1MzIxYTI1MzU5MTQ1YzAzNDQ4NWU1NTEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkzYWY0YzNjN2U2YTYyYmFmYWM1NDJiOGZlMzhiY2JmODI4N2ZjNGFiMmViYTg4NjdiZTJjNDU3ODI3OGI3ZDUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImJkOTk3M2YwMDk0NThiNjc5ZTA0ZGU4ODMxZjg0OTZjNTZmYWE4ZjYzYjNjNzg3MGQyN2M3MGM2MjY3NzM2MTIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImE1M2IyMzQ5OWY1MDMwZDdhZWJhZDQ4NDY0N2M3YmJlYzg3NzU5YTFhZDU1YWZlZGU3MmU5OWFiNmJlYjAyZDIifQ" + }, + { + "resource": "repo://src/store/mod.rs#L30-L52", + "version": "repo-lines-v1:sha256:cce27f447bc166d74cfcc26070194a141aeac612bf4f881f5bc175569a2d774b:eyJzZWxlY3RlZExpbmVDb3VudCI6MjMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ1NzkyYmVhMmFlZjdjYTBjMTM1MGNiMmM4ZmZhZTA1OGEzYzg5Y2VmZDdjNjlhYjBlYWJjY2JmNzY1ZmI0MjEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjEwNTc2Zjg3Njg0MWQ2MzhlMGQxNThjM2FkZDlhMjQzODg2MjVkMmZiYmRmZWI5NzMzMDlmMzc1ZDcyMjQ5ZjkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhhNzY5MTA1NDM4ZjQ5NGExMjQxMjkzMDU1NWYzNmNlOTA5YzcyNTUyNWQ4ZDQwYTJhOTUxOGYzMzhiMjg4NjUifQ" + }, + { + "resource": "repo://src/tunnel/spawn.rs#L143-L183", + "version": "repo-lines-v1:sha256:bcaca4f4fc2ee1a1785ed286f37be6cab3cc50dd18d12bb2bc3795ed2e68b7f9:eyJzZWxlY3RlZExpbmVDb3VudCI6NDEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQxYWMwNTlmNDc0ZjJjOTZiZGI5YzBkNjUwNmJlMjljZDhjM2M5N2RiZWNhMzUyOWY4NWY3YTI2OGUyMGI4ZDkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImExZGRhZGZjN2Y5N2FlZTY4NTQ5Y2Q3NWJmYmM3NzJjYTg4ZmJjMzRjZTY3NDg1MTIxZjJjNmIxMDk5OTliNDkiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImI5NjVmMjFkMzExNDQzNmRhN2RkZGY2ZmU4OGQ2MTEzZDUyZDU4OTk4ZjJhZGJkMGZjNTg2NDkwMDczNDBlNTQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjM1ZjZmNDY2ZTU5Y2ZjMzA3MDNjNDVmNzJhZjY1ZGU1NGFjMzYzZDM1NGNjMWEzMzMwMzUwYWNkNDUyNDM0YzQifQ" + } + ] + }, + { + "id": "claim_164d7ce57f574ea389ad47a3aa99572f", + "statement": "ssh_config export flattens CR/LF to spaces in every host field via `conf_val` (src/ssh/export.rs), so a field carrying an embedded newline cannot inject arbitrary directives (e.g. a `Host *` stanza) into the exported file.", + "evidence": [ + { + "resource": "repo://src/ssh/export.rs#L45-L50", + "version": "repo-lines-v1:sha256:22f5cac172c8f9c7ac1b43c5a2ca0a24cc9365d89e2f32e34ee7db443266a4a2:eyJzZWxlY3RlZExpbmVDb3VudCI6NiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNDU0NzNkYjYzNDAwODU0MTY4NmIzYWY4MDZhMWE0ZTY1YmI3Nzc4MjE2NzUyODNkYWU4YjVkZTY3ZmZkODEyNSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZjVhY2ZkYjgwMjVmNjA1MjBlNTcxNmNmNjc4ODU5YmVlMzliMThlMjZmMzVhZWYxNWMzMjlkZGIxNzA3OWYyMCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMTU0OTgzYjAxMDUwYzY3MTkxNzYwYjliODQxZDkzODliMGRhMjdlZmNiM2Y3OTViZWUxOGRmYTFjYmFmM2NkNSJ9" + } + ] + }, + { + "id": "claim_f7a3e37428514178991834d1969067a3", + "statement": "ssh_config import/sync prints nothing to stderr while the TUI is in raw mode (src/ssh/import.rs documents 'No stderr — import runs under raw mode'): unresolvable hosts are counted in report.failed or silently skipped, never echoed, because a diagnostic would corrupt the UI.", + "evidence": [ + { + "resource": "repo://src/ssh/import.rs#L24-L81", + "version": "repo-lines-v1:sha256:bbeb35e23564ee48d4a8beb38b67d09c59bc35e5e9a46f50dc8fc77f2d1872e2:eyJzZWxlY3RlZExpbmVDb3VudCI6NTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ5ZGQzNWViMTI0NzQ1ODdkNDc3ZjliZjA2ODRkZTYxZTU3ZGI2NDBlMWUwYTE5MjY4NWU2NjA5ZTBkMDFkMTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQ1OWNkYmFlYTM3NjJhNTFkZTQ5MTZmMTBjNjM5NjMwOTlkNmNiNGU4MmI3ZjM5ZmE2Mzk5NDdiYWIwMjhmYTciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjFiOTYxYzYwODYyNzU1NDg1Y2I0NGRjNTc1NzI4MWEyYWRjNDhhY2UwNTlmNTY5OTg2MWE4ZTMzMDU0ZDJmOTAifQ" + } + ] + }, + { + "id": "claim_4c73aa13a3314aa4a81857e53ea4756d", + "statement": "The `sshub host resolve` CLI subcommand (cmd_resolve, HostResolveJson) exposes only `has_stored_secret` — a boolean derived from resolving the pending secret — never the secret value itself; host record JSON likewise carries only the has_password flag.", + "evidence": [ + { + "resource": "repo://src/cli/host.rs#L97-L112", + "version": "repo-lines-v1:sha256:4ed148bcce92106e07e9feb8baa8f9b6c0b51a4cdc57aa1b7f6d76136a3fc896:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjYzOWYzNTc5MTk1ZGU0YzE0MDQ2NDBjYjc1OWZkNzcwYTVmYTM3M2E1ZjlkOWM0ZGM1ZDQ4OGUxYmM5YzM5ZGUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNjZGJjMDNiYzEzZTgwMjJkMzMwNzZmMDgyZjBlMjBmYTQ3YmExY2U2OTE2YzhhNmFmNDRkMzE1Y2RkNDczMjgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImYyOTVmYTYwMDRhMmYwOTA1OWRkYWJhYWZmNDFiYTljYWJjMWE3ZjVmMTM5NzdhYzZlN2RhYjNmZmMxNTU4ZGUifQ" + }, + { + "resource": "repo://src/cli/output.rs#L140-L157", + "version": "repo-lines-v1:sha256:7a01dc60b22c9070df928b53fe87bccfb6fdf7b360dac37fd1e89b4c88940b5f:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBhNWYwMTdkMmZiNWEyNWNkM2JhOGI4ZmRjMDI4MGIyZmUxN2Q2MThkMjcwY2IxNzAxNzdmZTE0ZTgzNGU2OTIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjcyNmQ2MzRiNjM5MDhiMWQyNjc0NzAxYTM5MTk3Y2YyZmFlY2NhMmQyZDAxN2Q5NmIyNzEzNGU1ZmJhYWM3ZTkifQ" + }, + { + "resource": "repo://src/cli/output.rs#L41-L47", + "version": "repo-lines-v1:sha256:39952ad06fe0ae62dddadf108bdf307dd79fc856c2436dd414f39832b7bf679a:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNWZhMWNhZjYzYTNlYWVhNTg4NGVjNjM2NjQ2NDU5NjJmNTBlMGI4YjEzMDkwMGVjOTg0NzVjOGQ1MjBiZjhhYyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiOGE1Mzc0ZjVjMjM5MzYzM2U2NzE3MDE5OWJhY2FlM2VjOTNhMmY2M2ZkNTNiODc1ZDE2YjNiMzQxMGVhYzViNSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMDUwNmQ4ODI3NjUwNjdlZTM1MWQzNjI0MGIzNmRjM2IzMmE3MzYxM2JkNmIyNDJhNjM5MDNkOTQxMzlkNTRhOSJ9" + } + ] + }, + { + "id": "claim_0399938984ce48a3bcf6c1188564770d", + "statement": "`sshub exec` writes an audit event with via = 'exec' on launch (and 'fail' with a spawn message on spawn failure) but deliberately never records the command string, which can carry a secret in an argument — the note is the constant 'cli exec'. (`--format json` echoes the command in the caller-facing ExecRecord; the boundary is the shared auth_events table.)", + "evidence": [ + { + "resource": "repo://CHANGELOG.md#L221-L227", + "version": "repo-lines-v1:sha256:719d6170a9540c58bd427faff581d828f246039e563310673a92ce02cddf51fb:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYzliM2IxZWIyOWZmNmFkNWNjYTgzM2RhNDJiNGFjOGU5MzgyOWEzM2IzODdhMWJjMTdkZjJmZGRhMjkxYTc0NyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYWQwZDQ5NzljYmUzMWVlNzhhOTFiZDQ1YzhhMjFlNGVhNTg5YjQ4Yjk0MTJjNWM3ZDlkNjk5OGJjNzAzYzljMiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYjM0Y2Q0ZTczOTBiYzNkOWJhNTlhZDY5YzVhNzFiZDAyNTYyM2QxNmU3ZTAzNjBiZjdiODkwODIyOWQ0ODEwMCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMDU4NDhjNzEzYmZjMTZlNzEyODEyMDcxNTZiYzliNDI1NTU2MGZlNDk0ZjBlMTg4NjM1NTAyODIwOTc3NmU5MiJ9" + }, + { + "resource": "repo://src/cli/exec.rs#L164-L177", + "version": "repo-lines-v1:sha256:048d403ac5cdb032a794849e88664e626d2e84407656f563bc5ec5850a825d21:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAzYmU0M2U1Mzc2NDMwMWQ2NDhhYmNlYTM1MWEyY2FkMmI1ZWQ5ZDcwN2MwZDBlOWNjNzE3OTdlOTNjYjZiMzIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImM3NDczMzJmYzM2ZTllOTlkZmMxOTRiNTE0N2NkM2Y3YWJlMmE2MzQyMzJmZjc2MTk1N2M1YTViYWRiNjk0MjMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjY4YjEzZTU0OWY5YzIxNmYzNGFjMmViYWMzNmUzYzFhMjM3NmNkMTZlYTQ4ZGI5YTc4MTJiYTFhMTdmZDgyOWIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhmOWRjZmU2YzNmZTZlYmNmZTZjNTIyZjZjZjM2ODdmMDJlOTcxYTI5ZTliZWU3ZjI5MGY3MDEzZTliMDlmYjcifQ" + } + ] + } + ], + "verification": { + "by": "openwiki/0.5.1", + "at": "2026-09-11T12:46:43.817Z" + } +} diff --git a/openwiki/.claims/workflows/broadcast.json b/openwiki/.claims/workflows/broadcast.json new file mode 100644 index 00000000..7b6ca419 --- /dev/null +++ b/openwiki/.claims/workflows/broadcast.json @@ -0,0 +1,298 @@ +{ + "schemaVersion": 1, + "pageVersion": "sha256:a8d0390b3dacfc2a7e1389973e8d97172b8ee98faea22da21530a5e9cb4d83bf", + "claims": [ + { + "id": "claim_abaa6bf9123140fbb389eba41fddb2e2", + "statement": "Broadcast mode runs one command across a host group or tag concurrently: `b` on the hosts tab opens a three-stage wizard driven by the AppMode variants BroadcastPickTarget → BroadcastCommand → BroadcastPreview, and pressing `y` at the preview barrier starts the run as a background job whose mode returns to Normal while the pool executes.", + "evidence": [ + { + "resource": "repo://src/app/broadcast.rs#L145-L205", + "version": "repo-lines-v1:sha256:11c47d7110009610afde3b71958a8e17d2e7293acb91b1f84ddd279d2113d051:eyJzZWxlY3RlZExpbmVDb3VudCI6NjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQxOTIyNWU1N2MwNGJhN2Q5YjQ2Njk5Njk0ZmQ3ZGI2YWFjNTY5OTY3YjRiOTJjMjE3NzQxZTllN2EzMjRiYjYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjliNjg2Y2YzODMxMGVhMjZhOWVmMTYyMDgzY2QwYTg5NWU4NmYzN2NkOWUxYjNlOWRlMjRiMmZmMTVkZDdlNWQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQ0MzEzMWEyYjA0OTgxOGJkZTUyOGZkYmI1ZDkwNjliNjk5ZDkxMjJjMGNiN2RkMzE5YmQyOWQ3YTQ0OTFmNzcifQ" + }, + { + "resource": "repo://src/app/broadcast.rs#L339-L398", + "version": "repo-lines-v1:sha256:d0df53e9d25060613f63833723481571bfcd987cc4fdc5fc049ffafb29128326:eyJzZWxlY3RlZExpbmVDb3VudCI6NjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImZiZTcxNGNiNTYzMTYyNDhhZTc5NDY5NjA2NGI5NTE3ZDdjNjM4OTk0NWRhZDFhMDZkZjgyMGUxNzUxYWE5MTAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQ3NmIyYzMxMjhlOWI0OTc2MzNmYmNlMGY0YmQyNTgzOWNlNTAxODIwMWFiODU4MmMwOGUxNjE0YjMwYTc5YWMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjdiZWViNmIyZjFkZjc0ZDg5NmU1NjBiYTA2MmI3NGRmZWIxMWQwOTYyYTljZjRjZDlmNGYyMzk3ZDc5YWI2YTYifQ" + }, + { + "resource": "repo://src/app/broadcast.rs#L404-L465", + "version": "repo-lines-v1:sha256:76da5b060d7c62bc034d2a8693371bc7a9c7bea070f507f985962864a5c6af98:eyJzZWxlY3RlZExpbmVDb3VudCI6NjIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjJkNmM4OTA2YmNlYTQ1YjQxZWI1OTk4NDJhZTJiNGI5NmQ4MzdmNzQyY2FmNDU1YWJjODI4YmVkNjNkMDBlMjYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE4ZjRlOTU4ZDM0YzAyYjlkZDBkOTEyNTM3MDdhNmFkNmU3NWZmOGIyZjRmZWM0ZTg0YmEzZDg5NDMzNGQ4NzgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk2YTQxZTNjY2Y2NWQ2Yzc1NDc0NzM5NGFiZTE0MjBmYjVmOTljZGE5NjZiNWE1MDMzNDEzMmU0NDYzOGVmM2UifQ" + }, + { + "resource": "repo://src/app/types.rs#L413-L418", + "version": "repo-lines-v1:sha256:b445a2fbf7b2820b33ad6f951f33e76be06a3ba6d0f0e2c1824b4414809e4c0b:eyJzZWxlY3RlZExpbmVDb3VudCI6NiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMDUzOTc4YmYzNDlmMDgwNmYxZDdjYzEwNGZhNWE3NDU1YjRkMGFlYTJlY2FlZGE5ZjRkZTYxZGNhODZlNDIxOSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDlhOWZiZjRkODIwYWM4NzZhMmQxYTAyYjY5YTk0MDFiMGM1MWVkYmNlNWMzN2UwNGM2OWQyMTE5MGQ4NWE5MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiM2FkOTA1N2UwMGVjODVmOGQ2MTNlNmU4ZmRhZDEwYjgyYjc4MGU4OGZjMmQzZjEwNWI5MzAyM2IyYWJhNWZhZSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiODM2ODJjYzgwM2JiODllZDg5YjNlZDg1YjlkYjVlZmUzZTUxYzViOTA0ODhmNmI2ODMxNmRkY2QzNjBhYmJiZCJ9" + } + ] + }, + { + "id": "claim_a6e75f5e5ad14b6fb4efebfdf62d71c8", + "statement": "open_broadcast refuses to start a second broadcast while any result row is still non-terminal (one run at a time), sends an already-finished panel off with the exit slide rather than yanking it, builds the target menu from user groups (the reserved Favorites group is deliberately excluded) plus the sorted, deduplicated set of host tags, and refuses with a notice when there is nothing to target.", + "evidence": [ + { + "resource": "repo://src/app/broadcast.rs#L145-L205", + "version": "repo-lines-v1:sha256:11c47d7110009610afde3b71958a8e17d2e7293acb91b1f84ddd279d2113d051:eyJzZWxlY3RlZExpbmVDb3VudCI6NjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQxOTIyNWU1N2MwNGJhN2Q5YjQ2Njk5Njk0ZmQ3ZGI2YWFjNTY5OTY3YjRiOTJjMjE3NzQxZTllN2EzMjRiYjYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjliNjg2Y2YzODMxMGVhMjZhOWVmMTYyMDgzY2QwYTg5NWU4NmYzN2NkOWUxYjNlOWRlMjRiMmZmMTVkZDdlNWQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQ0MzEzMWEyYjA0OTgxOGJkZTUyOGZkYmI1ZDkwNjliNjk5ZDkxMjJjMGNiN2RkMzE5YmQyOWQ3YTQ0OTFmNzcifQ" + } + ] + }, + { + "id": "claim_fe8ab2ee6c134520b037cc93c35002b8", + "statement": "On target pick, resolve_broadcast_candidates resolves managed hosts only (entries without a managed id are excluded) in host-list order, each selected by default, capturing per candidate `ssh_argv_for_entry(entry)` (argv[0] == \"ssh\") and the stored credential resolved immediately via `resolve_pending_secret` — the same path connect, detect, tunnels and the CLI use — so password hosts authenticate via SSH_ASKPASS instead of failing under BatchMode.", + "evidence": [ + { + "resource": "repo://src/app/broadcast.rs#L237-L287", + "version": "repo-lines-v1:sha256:21421921f814a4d13c73a5363ef96e9f261badf664e175a50beb8c702d26507f:eyJzZWxlY3RlZExpbmVDb3VudCI6NTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI2YTI4MTk0ZmNkMTQ0NjZlYmZkMmU3ODJjZjFmYTczYmQzMzY2YWMzZmM0YWVkODM3MzhhYmJmNTIxNzRjZTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE2MmVlNTdlNDFhOGMyMGFlMDE2NGRmMjYxNDM5ZGNjNDk5ZDM1MjA4MzQxNTE1MTZkMDQ3ZThkZWFlOGE0NjYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjUxYTA1MmZjMjc0MDI2NGJhZjc0NWU3NTg2MWY5YTIwODFjNGE0NjgwMzMyY2U0YjNhZWQ2Y2IzZWQwZDcyMDgifQ" + }, + { + "resource": "repo://src/app/types.rs#L587-L621", + "version": "repo-lines-v1:sha256:c440bc213de036dafd4dd18a625db0548387ae52ef77527a5c97505ef1893a97:eyJzZWxlY3RlZExpbmVDb3VudCI6MzUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImI3YzgxMmY0OTlhNDUzZjE5MDMxZmRiZGNiMTI1OGE5Y2Q3ZjhkOGVjZGJhNWNlYWU2OGVmZjI0ZWFkY2YyNWQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjczY2IxNWI0N2RmOWJhMzE1ZTIwNmNhY2RjNGJhYTU2NjBiZmU0MzliZWFmNDdlMTIzZTQwNWQyYWQwOGYyODkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjZmYjQ5NTM1Njg4MTQ2Yzc1MTA0NTQ2MmRiNTllNzI0OTRiNzA5ZTg5OWRkMzI4ZmQyZDRjZWZkOTc0ZjZlYzkifQ" + }, + { + "resource": "repo://src/app/util.rs#L7-L75", + "version": "repo-lines-v1:sha256:5f0977a3844bcf4df6666c15c8fd49b3bb2fa0e769afb7da9cded59bdc6f1963:eyJzZWxlY3RlZExpbmVDb3VudCI6NjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjE1YzdiMGUyYjlmNzJmMjI1YWJjZTVhNjQ2MmViN2E0NjBiMDg4ODA0MGVlMGE1N2I1MWE2MjllZjU2Njk1MGMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImI0ZTk2ZjczOWZlZDAxNTY3MjY3NDM2NDhlM2UzMWEzYmM2Y2UwMjVjYjZmZTM5MWFiYzM2OGU0MDk5NWMzYTAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijg1MzdlYjYzNGQ4Mjg5MjY4MTFkZWQ5NDE0YTMwMTQxMDNiMGMzMDdhYWVjYmVkMmI4YWZkOTNlZjQ2NTljNDkifQ" + } + ] + }, + { + "id": "claim_4c1651d3d99e4500bdd1e2bacc968a17", + "statement": "`src/broadcast/mod.rs` is a pure fan-out engine with no App or TUI dependency: it owns the shared value types (BroadcastTask, HostState, HostResult, BroadcastEvent), the CommandRunner transport seam, the real SshCommandRunner, the bounded worker pool (spawn_broadcast), and the reducer/view helpers the UI layer drives, making the engine testable in isolation like osinfo::detect.", + "evidence": [ + { + "resource": "repo://src/broadcast/mod.rs#L1-L15", + "version": "repo-lines-v1:sha256:93becf98db0c3b8a50c5d22c7966876c60b0c1f2bef9051128ae0f64f637fd0e:eyJzZWxlY3RlZExpbmVDb3VudCI6MTUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ5MTczNjEwNGE2ODk4ZDlkMGY0Y2JlMWNkMDgyNzhiOWQ1NTY3ZmIzOWQyMWRlZmU0YmVjYjlmZjBkNGM5YmMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVkZjY3OTI2ZmZiNDBmMDNjZjc0NThhYTJlYmQwNWFiYTE4NjY5MTY4M2QxMTZlZDYxYzVmYWM3ZThjOTg3M2IiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQ2NTEyM2E3Zjk3YjE2ZjczMTNmNjhmOWUxZTVjODVmNWRlZWZmZmRlYzVkNDI3MWY5MTg0NmY2MTg1MDcxODkifQ" + }, + { + "resource": "repo://src/lib.rs#L1-L2", + "version": "repo-lines-v1:sha256:8266b85f4726ac94a9ec42670824d8d4951012a12aa8aab0199eb6f5655ff7f4:eyJzZWxlY3RlZExpbmVDb3VudCI6MiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMTk3YmNlMGE2NjlkMGQ3ODBjMTkzZGU3MjAyMWE2YTAzNzRjOGFiZDUyNDlmNDBlYmU2MGQ3MzVlODI4YjE2NyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNWIxNDU4NWQyY2NlNmRmMmM3YjdmYTU3NDRhNDIwOWU2ODBjNjgxMTI1NTE5YzExYmY0NjUyNjEzZDQ2MmU3OSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjAsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZTNiMGM0NDI5OGZjMWMxNDlhZmJmNGM4OTk2ZmI5MjQyN2FlNDFlNDY0OWI5MzRjYTQ5NTk5MWI3ODUyYjg1NSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiYzEyNTI2MWI0ZjIzMTI5ZGRmYzM4NmUwMWMzMzY3ZmM0NDJjZWE2MjZiMTRmODcyNDU5ZWU5MzlhMGEzZDMyZCJ9" + } + ] + }, + { + "id": "claim_65d73b17f20049de8e34cc253dca8981", + "statement": "spawn_broadcast runs K = min(concurrency, tasks) worker threads (clamped to at least one; the app always passes DEFAULT_CONCURRENCY = 8) that pull BroadcastTasks off a shared Arc>> and emit BroadcastEvents on one Sender; the task channel is preloaded and its sender dropped so worker recv() never blocks, and an empty task list returns an already-exhausted receiver with no workers spawned.", + "evidence": [ + { + "resource": "repo://src/app/broadcast.rs#L427-L434", + "version": "repo-lines-v1:sha256:a8cc953850892c9a3e3181d6996ffba970ed00d33f644a39bab51f98699f2a4a:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMmI2M2M5NDM0YzEwNzRhNjMxOTE5ZmIyMDdmZjBiYjk2OTA0ZDJkZWUxMzE3NDIyZDA5MTlmYTcxNmRjNTQwNCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMWU3NWM3MGQ5NDYzNjI1ZTEwNzlmZDhmYzhlZTYyYWQyZTkyYzQyYmVhYWJmZDg1Yjc4ZDhlMGE0OTc5Yjk0YSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNjMzOGZkMjgxZjEwNjQ3MjE4NDNkMzRmMDc4Y2NjMmM0Y2M2OTgzZTVjODJiZDgyYmVmOTkyNjFlZDA2Y2YyMCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiYjQ5YzJhZjNlODRhNTYxM2ZhYjcwY2QxZDRhYzc5ZDE2YmJmNTE2NDBjYzgwZTkwNmNiYmQxM2VmNDlmNjhiMyJ9" + }, + { + "resource": "repo://src/broadcast/mod.rs#L25-L26", + "version": "repo-lines-v1:sha256:d8782c5db59717e86d688a65e4afb79cf62e9068f4effa7077c716368b6c8a21:eyJzZWxlY3RlZExpbmVDb3VudCI6MiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiY2NlNTVmMDRkYWVhNjQxNjQ4Mzk3NmFhMmMyNThjNzA0Mjc5YTljNTZmNWUzNGU1MWY4ODU2NDhhYzViYzVjNiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNzg2MTlmZmFjMWVlYWQ3ZmU1NGY4YmNhZWFiMGYxNzc4OThmYjUzMDNhMjVmNzZlM2JiNDJkNTE1ODFlMmFmOCIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNTVhYjc2MjY2ZGUxYTZjZjVhYTg4Mjc5MDEyMmMzYWY5NDJjNDNjYzIyMzVlNDJjOTA4NDliOTliYzc3NmJmMiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZWIwM2IzM2I3OTI1MWNlN2ZlZjhhNzU2OTgxOGNmMmYyNzkwOTJmNTI1ZWNjMTdiZmNlOTFkMzE0ZDhmYTM1NiJ9" + }, + { + "resource": "repo://src/broadcast/mod.rs#L286-L332", + "version": "repo-lines-v1:sha256:2ced52a67d1a5f3ae65ef055cb81d22a0afd5347d8f346a9521b5d44a66f4bdb:eyJzZWxlY3RlZExpbmVDb3VudCI6NDcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjViZWI2ZjY2MTMxMjU5ZmI3MGRiMDU5ZWZjYmY1MTI3NGJlNTcwMGM0ODY1NzI1MGU2OWIzNzZkOTU5MGYyNDIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQ4ODU5MjEwZWJjYjEyY2Y2OGM2MTYyMjJmYzU2MGMxZjY4ZTJmYTMxZjYzMDQ5M2RmODA5NTg4ODdkNTdhZjkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImY3NjM4Yzk5ZGRkOTkwMzJlN2I1YWI0NjQ4NjM2NzJjMDRkYmViYjdkZDZmZDRjZmIwOGViNDJjOGU0NDAzZWQifQ" + } + ] + }, + { + "id": "claim_6fc251635b0343728e7d085d1226b7cc", + "statement": "The broadcast pool guarantees exactly one terminal event (Finished or Failed) per task, including under cancel: workers that observe the cancel flag drain the rest of the queue emitting Failed{reason:\"cancelled\"} without running them, in-flight children are killed by the runner and surface as Failed, a runner that always errors cannot panic the pool, and senders dropped when workers end let the UI's drain loop see channel closure as run completion.", + "evidence": [ + { + "resource": "repo://src/broadcast/mod.rs#L286-L392", + "version": "repo-lines-v1:sha256:b96670565e7646ad4a08d337da7d8e714216eff58bc338be6bd711c04492a436:eyJzZWxlY3RlZExpbmVDb3VudCI6MTA3LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI1YmViNmY2NjEzMTI1OWZiNzBkYjA1OWVmY2JmNTEyNzRiZTU3MDBjNDg2NTcyNTBlNjliMzc2ZDk1OTBmMjQyIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0MTJjYTM0NWNjZjc1YmY5YzA4MDZiY2U2OTViZThkZTgwOGI3OTk4NDI1MWE3YTU0ZDIwMmNmNjEwMWRkNDUxIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJkODg1OTIxMGViY2IxMmNmNjhjNjE2MjIyZmM1NjBjMWY2OGUyZmEzMWY2MzA0OTNkZjgwOTU4ODg3ZDU3YWY5IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiI1NmZlZmJlMzZiYTdjN2E0ZWNhZTI5NmYyNDg4ZjkyOTIzYTYyNmY1NjJiNzk2ODgyZTA3ZWM0Nzk0OTM0ZWFjIn0" + } + ] + }, + { + "id": "claim_78ce53e1e5af421db01b5cc95cdc3af1", + "statement": "SshCommandRunner shells out to ssh with the same non-interactive splice as the OS-detect SshProbeRunner — `-o BatchMode=…`, `-o ConnectTimeout=8`, `-o StrictHostKeyChecking=accept-new` after argv[0], remote command appended as the final argv, no PTY — and enforces cancel plus the 60 s RUN_TIMEOUT with a 50 ms try_wait poll loop that kills the child, while stdout/stderr drain on dedicated threads joined after the kill so a chatty host cannot deadlock the other pipe; any completed exit code is a Finished payload.", + "evidence": [ + { + "resource": "repo://src/broadcast/mod.rs#L117-L270", + "version": "repo-lines-v1:sha256:ac7d73fdb49485d6425ff86a8b202afd75f8a148567288d966045011bb7041d0:eyJzZWxlY3RlZExpbmVDb3VudCI6MTU0LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiIyYzcyN2IzN2VkZjVlM2I3Yzk3MGVmNjgzMDQ1OTFiMWViMjNkNGE2YTMwMDNjNWIwZWI2YWViOTYwMGI2NWQ1IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0MTJjYTM0NWNjZjc1YmY5YzA4MDZiY2U2OTViZThkZTgwOGI3OTk4NDI1MWE3YTU0ZDIwMmNmNjEwMWRkNDUxIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJkZmY1MTYwOTQyMzY0Mzk2N2E4NzVmZWM2MDRkZTU4ZGYxMWE4NDdjNzRmYzIwODNkNjU2N2UzMjhhODgwNjRiIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiI1YjI2MmMzNjZhOTM3YzFjNjcxOTA2ODE2NTY3YmE5OGM2ZmNmOTVkYTEyMGU2YjRjYzY2YTQ0N2NkY2JmYmY5In0" + }, + { + "resource": "repo://src/broadcast/mod.rs#L35-L41", + "version": "repo-lines-v1:sha256:a341f058e63d6e47d2e95d82731a8b846c63eba795d6020a36c53a4b73a385a5:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZTI2OGQyNmU3OWFlOTAwM2JiNmI4NDAwMjEyOTUxZmYyMTc3NjgzNzEwZjljZjFjNzU4MTI2ZTA0MDFjYzcwNyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNWU0ZDM1N2IwZGMzODQzYmJjMjRkYWU3ZTQ4Y2NlNmRhMTVlYzAzOTcwYjlkZjgyYjI4YWI2ZGVmM2EzNTEyNiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMzIyNzk5MjVjNjBkMjkxYzYzYmFhZTY5YmNmMzQwN2MyMGE4ZDU1M2Y4Njc5NmU1N2JkZDkwZWYxOGFhYThlZiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZWFiODE1ZTBmM2ZjMDM3MTI0YzlmM2M1MjIyNDUwMTMzMTQzMjIxYmRkYWUyNTY5YzRhZGI0ZmEyYWZjYWNhMSJ9" + } + ] + }, + { + "id": "claim_9bf8f222b76e4a8287bf29df927334e6", + "statement": "Broadcast auth splits per task: with no stored secret the runner uses BatchMode=yes so key/agent-only hosts fail fast instead of blocking on a /dev/tty password prompt; with a stored credential it uses BatchMode=no and stages the secret through the same SSH_ASKPASS path a live session uses (session::askpass::helper_exe plus an AskpassSecret 0600 file removed on drop), holding the guard for the whole poll loop so the staged file outlives the ssh child.", + "evidence": [ + { + "resource": "repo://src/broadcast/mod.rs#L160-L206", + "version": "repo-lines-v1:sha256:8ac31437983d37bf91671e98fefac781d2ad06769ba2adc106a0de01082f855e:eyJzZWxlY3RlZExpbmVDb3VudCI6NDcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBhYTlmMTAzMmIzNzkxYzA1ZDMyNjY3ZDBhZDA0ZmFiMjdiNjg3MWVlZjZkZGEyMGUwNTk3MzhmNjQxODA1OTgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjhlOGQwNWFkNDYwZDFkZTY3ZmE1YTI3ZGMyZGRkODI0MzZhM2U2NWYwZTc1NzMwMTc1NmUzZmRhNDdjODEzMGEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjNiMGVlY2MzMDU4Y2Y3ZTQ5OWQzYjBlMWQ2ZGYzOWJhMTRkYjBiNTk0ZjJiZjk2M2NjODUyNDNhNDdhYTUzMzUifQ" + }, + { + "resource": "repo://src/broadcast/mod.rs#L43-L54", + "version": "repo-lines-v1:sha256:265e8f8d44f7b9a6adf3ff299cb4f016ea240a98f7075e837fb9d14cface96bb:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQyNzk3Zjc1ODcwYThmNzhmZGM2OGNkYTUwNTJkZDNmZGU4YTViNzUyYzZiNjRhNDQ4OTUwZjI0MmMyM2M5NWQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjdmMmRlM2RkNjAwZjdhY2ZkZGE4MTYyNGEwZjA4YjQ1OTJiMDk0MGYzOWEyMWRkNjE3YTAzMzM0YjJiYjNiNjkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjRiNzBkOThjYWRiYWM2MjllMDQ0YzFiNzJkYjEzMDc4ZDNkM2RmOGE1OTFjMTQyZDBkOTNiOTQ4MDlmNWY1Y2UifQ" + }, + { + "resource": "repo://src/session/askpass.rs#L56-L103", + "version": "repo-lines-v1:sha256:0a53f95abdfacc1b474d8c5d5e3fd1c569d839a8605672c5445e185ff6c4f065:eyJzZWxlY3RlZExpbmVDb3VudCI6NDgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVjN2Y0ZTU1Y2E4MWVhNTliY2NjNmFlMjM4ZDUxYWNhZWQ2YjFlNmU1MjUwMTM4NTlhZjc1OGEzMmZkZmFmMDkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFkNmY0MWNhNzg2MDBhMjBkMGRiZTI1YzE4Y2I0ZTVjMGNiMjE0NmYwMjkxMDM3MTRkZjk2Y2NhNDcxNDM3NWQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjZiYTk1NWJlZGVmOGRjMDZkYWRlNGYzYzRiYjg2MzcyODY5MDBmMTExOGM4ZDgyNTk0YzIzN2QzNTYzMmI2ZGEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImYyMWNjMzlhOTgzZjc3MGEwN2Q3NzM1NzIyNTBmZjRmYzBhZTdiOTYwNWVmZTFlNzkyZjQ3YjE5OTg0ZDQ5NjIifQ" + } + ] + }, + { + "id": "claim_a87ac125c69841d897c0839f12a9d038", + "statement": "apply_event is the pure reducer folding each BroadcastEvent into the matching HostResult row in place by host_id (Started→Running, Finished→Done{exit}+stdout/stderr, Failed→Failed{reason}), preserving row length and order, never resurrecting a terminal row on a stray Started, and ignoring unknown host ids; failures_first is a stable render-time index sort (Failed and non-zero-exit Done rank first, then Running, Pending, clean exit), and a non-zero exit counts as a failure in is_failure, failure_count, and error_text.", + "evidence": [ + { + "resource": "repo://src/broadcast/mod.rs#L394-L491", + "version": "repo-lines-v1:sha256:4b35d1b082dd5be2a06db685bde45f9fe243537a023340d9d0888ab31d54cece:eyJzZWxlY3RlZExpbmVDb3VudCI6OTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjE5ZTIzZGQ0NzNmMGY2NmYzZjczMmFhMzEwYjQ3YmNjOTIzODVlODJlNDg2NDM1ZmI3MjhiY2MyZTk2MzYzMzciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjdiYzk4ZDZiMGRjNDkyYzUwMTZkZjEwODA1YTNhNTRlYjljMTg0YTVhNjEzN2U3ZjRiZWNmYTBjMjIwNTUwZDAifQ" + } + ] + }, + { + "id": "claim_bc912513ea3a4a5babb1cd9614fb6df0", + "statement": "The live run is a docked bottom-right PanelId::Broadcast panel joined to the issue #18 focus ring: on `y` the panel slides from a centered spawn_rect to docked_rect over ENTRY_ANIM = 600 ms (reduced motion sits at the dock), deliberately starts unfocused so the completion countdown is never paused by accident, is reachable via Alt+arrows from the ping panel and ssh-log strip (focus hops onto it are suppressed while no run is live), and `z`/Alt+Enter zooms it full-body into a failures-first selectable list with the selected host's stdout/stderr detail panes; the Broadcast panel is excluded from the grid zoom-morph animation and the hosts-grid zoom dispatcher because it has its own slide path and no home in the bento grid.", + "evidence": [ + { + "resource": "repo://src/app/broadcast.rs#L404-L465", + "version": "repo-lines-v1:sha256:76da5b060d7c62bc034d2a8693371bc7a9c7bea070f507f985962864a5c6af98:eyJzZWxlY3RlZExpbmVDb3VudCI6NjIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjJkNmM4OTA2YmNlYTQ1YjQxZWI1OTk4NDJhZTJiNGI5NmQ4MzdmNzQyY2FmNDU1YWJjODI4YmVkNjNkMDBlMjYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE4ZjRlOTU4ZDM0YzAyYjlkZDBkOTEyNTM3MDdhNmFkNmU3NWZmOGIyZjRmZWM0ZTg0YmEzZDg5NDMzNGQ4NzgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk2YTQxZTNjY2Y2NWQ2Yzc1NDc0NzM5NGFiZTE0MjBmYjVmOTljZGE5NjZiNWE1MDMzNDEzMmU0NDYzOGVmM2UifQ" + }, + { + "resource": "repo://src/app/keys.rs#L293-L320", + "version": "repo-lines-v1:sha256:1a0bba13ae8985f9803706de7a29505d893eecea9f34d640e843028e590c9ec2:eyJzZWxlY3RlZExpbmVDb3VudCI6MjgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImY2OTFkZTNlNjk0ZTc5MmEwY2QyYzQ3Yzg1M2ZlMDk4YTYzMDc1NjM1NmNlZmFlOGFmMmQ0ZjM5ZDlmZGEzNTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU1MjJjNDJjZDE5YzZkMDNkODA4YzQ1MjcyMDU1MjgyNWI3ZWExYzViNDkxYjIxODUxMWNhYzc0MTVjZjlkOWIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjJiY2UwNzAzMGRmZGE1ZjFiYjU3NzE3OTA0YWI1Y2E2OTM3ZWI2MTZjMzA1ZjZjNmY1NTAyOGY0MjNiNDhjOTMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImNhYjhjODdhZDNjNmRiOWU0YzM1YjJhM2MzMTlmNTRmMGNkYmJmZmZmMWY1N2RjNTYxMjY2ZWMxODgxZTgwN2QifQ" + }, + { + "resource": "repo://src/app/keys.rs#L376-L393", + "version": "repo-lines-v1:sha256:a22a69dc31f7aa3fcc5fce2f344cf9d0e5dfe21da9280238bd508009940c5ade:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI3NWRmNTYyYjhkOTRhNmNkN2UyZWM0NzU0YmRkMjRkMjU1YzMxZDQxYzg4MjE1MjNmYmUyMzUzMTI1ZWU2MTQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjRmYzQxNGMzNmM5ZmI0ZGMwOTQ5OWYyZGI5Yjg4OWFlYzA5MWUzNGZkYTc1YTg2ZTJiZDI1Yjk4ZjQ1YTZkNjIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImFiMTM5NGE2Zjk0MTllMDJjM2FmZmMwZmZhMDVkNDc5MDg2ZTQyNTM5YTcwZDA4OTU2NTE3MGY2Njg2NzkyOWQifQ" + }, + { + "resource": "repo://src/app/types.rs#L107-L150", + "version": "repo-lines-v1:sha256:192b06854f38ff41a65a0ddc878e59c413894580a99394378a7e0d66a11770c4:eyJzZWxlY3RlZExpbmVDb3VudCI6NDQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFhZmFhNWNkZTc5MGYzNmFhOWViNTdkNmY1M2Y5Mzc0Zjg2MmQ2NjdkOGQxZGExMmY5NTg3NmI3Nzc1NGEyMDYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijg0NmQ2ODAxNzU3OGViMDVhODBjMGQyYTY0Y2EyMGU3ZGZlZmI2NDlmMjcyNjE3YTc4YjgxOGMyMWNhNTAwZjkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUifQ" + }, + { + "resource": "repo://src/app/types.rs#L84-L88", + "version": "repo-lines-v1:sha256:5c4a7f76abc2d9e9b449a819e96dc3cd16f59991e3ce9b147c18f8b0407d84f3:eyJzZWxlY3RlZExpbmVDb3VudCI6NSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiM2I5YzhmYWE0ODZiODQ3Mjk5ZTMyMmY1YjYyZTc2ZjhmM2JhNWZlNzYwMWVkMzA1ZDRkM2UyZWEwOGYwMDAyOCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNGU0OTNlODliZDBhNzQzN2M3MzJkMzlmNjQ1YzY3OWM0NTY0MGFmMDEyNzUyYjNlYTExNGIxYTUxOTA4MDhjNiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNDdhNzI1ODcwMmQ2MDM1YjdlMmYxYmI4OGU4MDIwYTEwYjBkZDhhNTgwNWY2MzUzMDA4ODQwMTAzZjFlZjRjNiJ9" + }, + { + "resource": "repo://src/tui/mod.rs#L1617-L1631", + "version": "repo-lines-v1:sha256:bd4fc48ade1a26b7e0ef469b3d455834c9ad9b1ac19cc7595c4f0b7f0d70ad94:eyJzZWxlY3RlZExpbmVDb3VudCI6MTUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU3OGYzNGQzMDQ4ZjlhZTgzZDdkN2VmMTRjNDQzZjVhZjY4OThmOGM0OGYzOTkxZDU5NWFlNDVlMDMyMWZiOTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImVlNTczN2RjNzE0YTAyNzVmOTc4NjgzYjA1M2JiMjMwYzYxZGI3NzJiOGFlZDc1NjQ0MWVjNmU3NTVkZGI3NmYifQ" + }, + { + "resource": "repo://src/tui/mod.rs#L777-L800", + "version": "repo-lines-v1:sha256:aed8e614e602891b2351c32c8242f764bc8f431a3f9cadae480a38b50b21deb9:eyJzZWxlY3RlZExpbmVDb3VudCI6MjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjZmNjM2NTI4NTc2ZTI1YWY3ZGUzNTY2MDc1MTEyNDg2ZjI1ZGYzMjEzMzY5ZDdhYTkyMTY0YTYwMTUwNTE5YTciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImNmOTU0NDc2YTdmNDE2OGQ3YzU0YjhlYWNiYjFiMWNmODc0OWNlNDk2Y2VlYjFmMzQxNjhhYzQwNzFiN2Y2YWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImEyOWZjZjE0NmZmOTg1ZjA2MDQ0ZDA0Y2UyYzkzNTgwODJlMzQ0MjM1NDY1NTIyMDk1ZDIzYzA0OTY1MGRkYWQifQ" + }, + { + "resource": "repo://src/tui/screens/broadcast.rs#L244-L404", + "version": "repo-lines-v1:sha256:6b92961e6379e3d701781d39e7dacb7998e5a1d825e3922d58b37bb2cd1f9baf:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYxLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI1ZTNiMDhkYTUzMTdhNzYzYTc0YTI5YzczM2M0ODRhNzg2ZjY2MjkwMzQyMWE2MzUxZTExMDUxZjg1ZTMzYWJjIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0MTJjYTM0NWNjZjc1YmY5YzA4MDZiY2U2OTViZThkZTgwOGI3OTk4NDI1MWE3YTU0ZDIwMmNmNjEwMWRkNDUxIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiIxOWU3NjM1NWNmMTE5OTQ4MGMzZjk5ODM2YjE0ODZjODQxZDg5MWZhYWQ5MDRjNjA4ZTA5MzAyMmQzNzU3YWVlIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiI2ZThiOGU2Y2Y1YTdjODViYmQ2YjE5NzhjNWI4ODdjZTA2MTM1MDAwMDU5NmEyYjVjMmZjNjJhY2VhZTM3ZDY5In0" + } + ] + }, + { + "id": "claim_255adf90e3774e12a14a0ed051d45260", + "statement": "The docked panel follows the BroadcastPhase lifecycle Running → Settling → Paused → Leaving → removed: when every row is terminal the phase arms Settling with the 6.5 s DISMISS auto-dismiss countdown (a depleting gauge with a `dismiss Ns` label), focusing the panel pauses the countdown and unfocusing re-arms it with a fresh timestamp, the elapsed countdown plays an exit slide off the right edge into Leaving, and tick_broadcast removes the panel when the slide finishes, stamping broadcast_panel_gone_at so lingering toasts animate down into the freed space.", + "evidence": [ + { + "resource": "repo://src/app/broadcast.rs#L471-L490", + "version": "repo-lines-v1:sha256:e0a45ffe9d085b529865e6de54ab785f5315260f1707bb745195268872da9f86:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkyNGY3MDIzMmYwOGJmYzQ1OTlmNTg2YzhiNjVjMjM5M2UxYjQ1NWQ5ZWYzMzBjMjZjNDgxZjhjZDZmZjg3M2QiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE0ZjZlNDk2NDQ5NDM2NmZhYjJjODI5ZDA5NDg4MjM3ODNjMTA5Y2FmMGEwYmM2M2VlMWE3YjdkOGE5MGE0ZDEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImM0Njk2OTFmZjZhOTBhZGNiZGU0OGYyNmFiOTZkZThhZjU0ZmU3ZWY5MTZhM2YxMWRhYjExNjYxZjBjMjI0MjAifQ" + }, + { + "resource": "repo://src/app/broadcast.rs#L495-L645", + "version": "repo-lines-v1:sha256:4a19dafe4a6672ab1d9f4fa53953e25854c2be4140f4e1ebfd840661de7a6e5f:eyJzZWxlY3RlZExpbmVDb3VudCI6MTUxLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiJjZmViNDE2ZGRhMjYwYjgyOTdhOTY0YWQ3OTRmMTE3ZDg4NzRiZDk0OGRjNDExN2IyY2Y0NzA0Y2FhOThlZjhiIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0ODkzZjQwZTM3ODFlYjg5NmM4MmQwNDZhMDgwNjdhMTFlMzVjZGYyZjFkZmFmMjZmZWE5MGI4ZGI2Njc2NDEzIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI2NWM3N2E2YWI1OWZlYmRjNGM5ZjcwOTE0Zjc4NDM5ZWE0YTJhZjE3YTFlYjYxY2ViNTA4ZjJhNDk4YjMwZDg3IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiJkYzVhMTkwNGU4N2VjYWRhOTcwNThiMWQyYmI4Yjg2M2UwZGFkMWNhN2IzZjVmMmYyZjhmOTI4OGQxNmM1ODQ5In0" + }, + { + "resource": "repo://src/app/types.rs#L578-L585", + "version": "repo-lines-v1:sha256:6d5928cee4fa039039ea553f6958e1ead2543f9c56ff6de6a66029dc3fa70589:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYzdiODgwNDJiZmMxODFjZWUzOWMyMGViOTUyYmZlNzUzZjgxNjVjZGFiZjU3YmQzYzliZDUwYjhmNGJhMDRkOCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiODgwZDJjNTZmOGFkMzAzNWZhNGJiYTQxNmYwMjc4MjZmOWFhZWIyZWM0ZmUxOTdmOGZlZmYzZDI1ZjM2MDVlNiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiODE5ODM3MDFjZTJmMWE5ZGE4NzczOTljNDI1ZjFjODE0MDlhOWNiNzY3NGI1ODZiYTczZDc0NzYyZjQyYWY4MCJ9" + }, + { + "resource": "repo://src/tui/screens/broadcast.rs#L406-L458", + "version": "repo-lines-v1:sha256:681696b9a86d2d01ae7f5d0674d8cf0291cae8116cff3ac12a3f17bf74631524:eyJzZWxlY3RlZExpbmVDb3VudCI6NTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjEyMTQ3OGI2MTZjNDk3NzYzYzZlZjAzZDU2MDA1YWExN2QyY2FhMDc4NWRjZDMxYTAxOTZhNWE4NzdkNGQyNTkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImZmMmMwZWE0ZDgxYmQxYzMxNzU1NjMxYWQ4Y2JiYTBjY2E2NTE2OThlZjc0NDM5YTA4YjliZjVjYTAxMWJhN2EifQ" + } + ] + }, + { + "id": "claim_e4b955a2378141278ccdb78a11d49f60", + "statement": "Cancel is the `x` key (KeyAction::BroadcastCancel, default `x` in src/keybinds.rs), claimed before any other binding and honored regardless of focus: with a live run it sets the run's shared AtomicBool, which makes workers kill in-flight ssh children and mark un-started tasks Failed{\"cancelled\"}; with no live run it clears lingering error toasts.", + "evidence": [ + { + "resource": "repo://src/app/broadcast.rs#L647-L654", + "version": "repo-lines-v1:sha256:ca42b3382c5e396fac4f71eba6077fc16a950887e172fb9a0fec2e57c5fb0790:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiOThmMzBhZmE5ZWY4ZDcyY2RmZTY1NDhjOTcxNDI5MTYwYmE2OGRjNDhlNjdhZGE1Njk0N2MyZjQ4MzFhMWVhYiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMTYyZWU1N2U0MWE4YzIwYWUwMTY0ZGYyNjE0MzlkY2M0OTlkMzUyMDgzNDE1MTUxNmQwNDdlOGRlYWU4YTQ2NiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjAsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZTNiMGM0NDI5OGZjMWMxNDlhZmJmNGM4OTk2ZmI5MjQyN2FlNDFlNDY0OWI5MzRjYTQ5NTk5MWI3ODUyYjg1NSJ9" + }, + { + "resource": "repo://src/app/keys.rs#L128-L157", + "version": "repo-lines-v1:sha256:f5b3c6aeb49637aa1f77bcdb3ad3c99ac7f5eff28fc90b29c76a14ff291de1cd:eyJzZWxlY3RlZExpbmVDb3VudCI6MzAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImM1Y2QzOTQ2YmU3N2UxOWM3NDk2YTExZDlkY2MwZjczNjU1YzhlZTQyNDIyM2UyYWYwZTEyYTM4ZTA0MTQ1MWMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjFhOTExYzc3MzU3NjIxNjUyNmQ4MDViNTA0YzBjNmQyNjA2Y2M4YzEyODdhNmEzYjBjNDY0MzM2ODBmZmFlZjUifQ" + }, + { + "resource": "repo://src/keybinds.rs#L506-L507", + "version": "repo-lines-v1:sha256:d70470988d56331ca2c431edfb54e202a1939087d611df77a009b3eab31e7c69:eyJzZWxlY3RlZExpbmVDb3VudCI6MiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZmQ4ZDA0ZWEzZmRhODNiZTE5MTBjY2UzOTNhYzEwODZmYjFiZTgwODRlYzMyYTkzNDljMzJlYzcxNjYxY2QxNyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNTZmMzBhZmM1NjNlNTZjYTA5Yzk4NzRiNWU4NjYwMzBmMzY2YWZhMDcwYjA3OWY0Y2ZjN2UyZWMxNGE4MDAxNCIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZDI2MDU0ODI5YjMwNTU1NDJmZmZmNjkyZTU4ZGE2NmM5MDRkNDZiYmYyNTUyOGRiNzhiODc4M2YzMDczMTQxMyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiOTdjYTFmM2NkNTI5ZmI0MGY5MzQ1ZmJlN2Q3OWEwOWUxOTg3MTM0YjdlZTI2ZWMzNzNkYTQxZjkwMGZlYTIwMyJ9" + } + ] + }, + { + "id": "claim_b1e3e220620349659cfb4261b74e3e85", + "statement": "Every failed host raises an error toast: for a non-zero exit the trimmed full stderr (or `exit N` when stderr is empty), for an ssh-level failure the runner's reason; toasts slide in from the right above the docked panel, hold TOAST_TTL = 10 s, slide out over TOAST_ANIM = 300 ms, size their boxes to wrapped text capped at 6 lines, wear the same error color as their row, and can outlive the panel because toast expiry runs in every tick and the stack re-anchors into the vacated bottom-right after dismissal.", + "evidence": [ + { + "resource": "repo://src/app/broadcast.rs#L495-L562", + "version": "repo-lines-v1:sha256:2ffcd5931cdecc13ebb128261bf7798ca3b154b89789435465936c3135fcc2ad:eyJzZWxlY3RlZExpbmVDb3VudCI6NjgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNmZWI0MTZkZGEyNjBiODI5N2E5NjRhZDc5NGYxMTdkODg3NGJkOTQ4ZGM0MTE3YjJjZjQ3MDRjYWE5OGVmOGIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRjZGRlYzcwNzMwYjlkMjA3ODk5MzNkYzkxNmIzZmFlZWNiYzQ5NGEwZGJkNDNmYTUwMDZmNzA2ZmRjZTczOTIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjY1Yzc3YTZhYjU5ZmViZGM0YzlmNzA5MTRmNzg0MzllYTRhMmFmMTdhMWViNjFjZWI1MDhmMmE0OThiMzBkODciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjM0ZjdiNmQ1NGE4NzM2YTE2MmZhMDM5NWJkMWQzMGVhNWY4NGU3OGRhMzYxNTJjZmE4YWU3N2U2YzU1NTkyNTMifQ" + }, + { + "resource": "repo://src/app/types.rs#L568-L576", + "version": "repo-lines-v1:sha256:467ed7f00cfcf915a840cbe55e8efa85ec592bc04ea9799438f5cdcb8a973443:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMGZkYzA3MWM4YmVmMzQwNzJkOWI1YzMwNmIyMWU0ZDM3OTdiMGEwNmFhODg3YjEyZmY4NDdhYmUyMjg1ZDRhOSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiOTMzMGRjNzhjMGRjYTUzMWMzOTJiNzlkZTA0MjQxYjY4NWIyOTlmYTM3NjFjNjBiMDBkYjllNzg1OGY4NGUxNSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNTEzNWE1MjU3OWM5OTBlYTdlYjQ3MjE3ZWYyZTViMTA2YjhkYzc3MWJjYjI1ZjhjODBiNmQzNDU2MzkyOGVlZiJ9" + }, + { + "resource": "repo://src/tui/screens/broadcast.rs#L460-L543", + "version": "repo-lines-v1:sha256:915e20238825bedad61a27878e207e97402299fe9b8797f98a5e900f87933a57:eyJzZWxlY3RlZExpbmVDb3VudCI6ODQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjcxYzcxZjQ3ZWNjZjU0NmI2YTBhNTk0MWQ4YTIzMWY4ZmRlZDVkYzRiNWJkOGU1ZTBhMDZiYTA4MzgxN2NiYmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjY5ZmEwZGMzYjU2MzNmZjM2ZGFlMjUxZjM3NDg0NGFhODc3Yzg5YjdiODYyZjVmOGY4ZTI2MDBlYWQ4YjE1NjkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE4MzQ2MmVjNWYwZjRkNjQyOTZlMWU3MWY0OWRiOWVlODIyZGEyMGY2MGRiZjM2MzZjNGMzYmUwOTkxMmIyZmEifQ" + } + ] + }, + { + "id": "claim_ac02193f9c914233b140429baac2169b", + "statement": "tick_broadcast is called once per event-loop frame after the other worker drains: it expires finished toasts before its early return, drains BroadcastState.rx via try_recv folding each event through apply_event and spawning a toast per failure, retires the entry slide, arms Settling on the first all-terminal tick, writes the audit trail once, and drives the countdown phases; while broadcast-driven motion (entry slide, toast slides, post-dismissal stack drop, zoom morph) is in flight, App::animating shortens the main loop's poll window from 50 ms to 16 ms so the ~600 ms entry slide renders at ~60 fps.", + "evidence": [ + { + "resource": "repo://src/app/broadcast.rs#L16-L143", + "version": "repo-lines-v1:sha256:ce28e168d9701c51291394881a768ab679be31dec8c7355f6657ac9bc1d0ecf7:eyJzZWxlY3RlZExpbmVDb3VudCI6MTI4LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiJkYzk5NWQwZmYyMmQ0YmY1MmI2YzQzNWIzMWNkNGRhYTNlNThhOGY1ZDMwMTM4MDI5NTAwMmVmNjAzMzliMGY2IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0ODkzZjQwZTM3ODFlYjg5NmM4MmQwNDZhMDgwNjdhMTFlMzVjZGYyZjFkZmFmMjZmZWE5MGI4ZGI2Njc2NDEzIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI1NzY1YzNmMTI3MzMzNmI3ZDFjYzdmOTY1ZjE4MTI5ZWMzMjE3YjY4OWQ4YjUzODRkMjhkZWZhN2VkYjBlNjM4IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiJlM2YzY2JkYzczYzk5MjM1NDRiYjgwYmY5Y2YxZDM3M2U2MDJkNjZiMDgwZjJhZDYxZjg1NjM3Mzc0YzJjZjdkIn0" + }, + { + "resource": "repo://src/app/broadcast.rs#L495-L645", + "version": "repo-lines-v1:sha256:4a19dafe4a6672ab1d9f4fa53953e25854c2be4140f4e1ebfd840661de7a6e5f:eyJzZWxlY3RlZExpbmVDb3VudCI6MTUxLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiJjZmViNDE2ZGRhMjYwYjgyOTdhOTY0YWQ3OTRmMTE3ZDg4NzRiZDk0OGRjNDExN2IyY2Y0NzA0Y2FhOThlZjhiIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0ODkzZjQwZTM3ODFlYjg5NmM4MmQwNDZhMDgwNjdhMTFlMzVjZGYyZjFkZmFmMjZmZWE5MGI4ZGI2Njc2NDEzIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI2NWM3N2E2YWI1OWZlYmRjNGM5ZjcwOTE0Zjc4NDM5ZWE0YTJhZjE3YTFlYjYxY2ViNTA4ZjJhNDk4YjMwZDg3IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiJkYzVhMTkwNGU4N2VjYWRhOTcwNThiMWQyYmI4Yjg2M2UwZGFkMWNhN2IzZjVmMmYyZjhmOTI4OGQxNmM1ODQ5In0" + }, + { + "resource": "repo://src/lib.rs#L450-L457", + "version": "repo-lines-v1:sha256:049776f7f55e17f11f452e2c338dcc52fc75da0bb49e8d03c686bf9de086ee76:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiOTg0NzFhMmI2MjlmMDhjMGU5N2VmZjdhMTcyNzgwNWM1OTI3NjYwNTdiNjIyMjA0MTI3ZGNjYTAwZThjYTFiMiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYWQ5ZTEyNTQ2NDI1NTc2ZDNkZWFlOGM5YjlkYmYxMjhkNWQ2ZGRhZjVkN2ZlODU3OTM0OTI3ZGVmZmEzYjlkZCIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMzVmNmY0NjZlNTljZmMzMDcwM2M0NWY3MmFmNjVkZTU0YWMzNjNkMzU0Y2MxYTMzMzAzNTBhY2Q0NTI0MzRjNCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiOWZmOTgzNDQ0NWU4YmZkOGE5ZTBkOTAwNTc5ZGYxODc1ODRkYTE0ZGVhM2JiOWVhNWY0YjkzMDBmMDMwOTg5MiJ9" + }, + { + "resource": "repo://src/lib.rs#L546-L547", + "version": "repo-lines-v1:sha256:8dbca11dcd68fa85432d25339f24a93997cea64e56bbfcc3607a2846606c6253:eyJzZWxlY3RlZExpbmVDb3VudCI6MiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZDJlNzY5MGM5YTI1YTUzNjc0ZDNhYmNjMTkxOTBiYTc0MTE0MTRmZDljZDE3NzBmNThkMzQ5YjRjMGNlZDY5MCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZDgxOGUxNGQ3NTUyY2ZkNWUxOGU2Y2VhZTBhNjRlNjE5YTk0MTY1ODk1MjUzNzA1YjZjN2JjYWFiNTdmMjk3NCIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiOTk5YmRlZmVlYWNhNjM0MGUwMjkwZGM4NmU4ODljODlkNWUxODlhYTViYTQ0YjU4NWIzYjcwNzM0ZjRhNjJkOCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMTg3YTM3NWM4MzM5MzFhM2NlNDQ2NDZjMjkwNTE0ZThiYjExZDk5ZmI2OTA3OTNkOGQ1ZDhhZDNjYWRlNGNjZiJ9" + } + ] + }, + { + "id": "claim_ede9f8c2e8fd4ab7a96b7b4b87ab2057", + "statement": "The audit contract writes one auth_events row per host at completion — guarded by BroadcastState.audit_written so a second tick never duplicates rows — via the same store.log_auth_event the connect/session paths use, with via = \"broadcast\" and no username, using the canonical status vocabulary: `launched` for exit 0 with note ` (exit N)`, `fail` for a non-zero exit with the first stderr line folded into the note (via the shared error_text helper), and `fail` with ` ()` for ssh-level failures including \"cancelled\"; full stdout/stderr stays ephemeral in HostResult and is not persisted.", + "evidence": [ + { + "resource": "repo://src/app/broadcast.rs#L564-L601", + "version": "repo-lines-v1:sha256:627a8c8dfc61ba6df468dc4e38b49c60a14d2d8f3840a4e1a5d3d36141c3bbff:eyJzZWxlY3RlZExpbmVDb3VudCI6MzgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU3NWI0NDI5ZWFmMjAyN2NjNDRkYjJkMzAxZTRiM2I4ZjFlYjRmYWQ2YzE5NzBmODUwZjFlOTYzMDkzNTU3MDkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImVjY2NiNmEzNWM1OTAzMDJjZTZjMjU2NmQxZDVmMGRlY2NlYmVhN2NiODg1ZmE0NjdjMmY3NTMxZmZkZTBjZjAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjkzNzE2YWJkOTBkMzMyZTg3MGY3YWNmZmYyODE2ODEzYWQ2MzlhOGIyNWU4ODVlYzU2NTY5NGRiM2EzMjJjZWIifQ" + }, + { + "resource": "repo://src/broadcast/mod.rs#L466-L477", + "version": "repo-lines-v1:sha256:371b0ee80b3a5113dbe58f16c7b062e1bc472af9c8fcb2179d44bf4753c6987c:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjY4NmYwZjcyNjU3YjJkMjY5N2RhZjIzYzFlNGFiOTJiM2EyYWUzNTJkZmYyY2ZiNWU0NjdlMDhlYzU3ZDc2MTQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjcyYTA0Mzg1YmJmZjkzNzBlMTU3MjJiNTNlZWZhMjcxYjVlOGRkMjE2NDc1YWIxZGJmYmMyMzQ4Y2FhNmY5YzkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjBhZDg2NDU0ZTg0YmE2MzlmNjU3YWUyY2YwYzQ4M2M1ZWIzZDYyZjY0NzZmYWFhNTIzNTE4ZjliYTcxZjUxMWMifQ" + }, + { + "resource": "repo://src/store/hosts.rs#L655-L673", + "version": "repo-lines-v1:sha256:473d2505cf1cdcef91b0afdacf7c15dec7d9efc8e118ecd54cc633045490cb4a:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNhZWRiNjVlODdkN2I4MjczZWM4ZWExNDY0YTBhZjFkZjk2MjBmYjE0ODZmNTYxMzE2MGE2NzRiN2E3OTBiNjkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjljMjM3MDg3MWQ5ZjJmYzYzNTQ2ZWFmYzg5ZDc0OGFiMzg0YzBiZmQ4ZDE0ZTA3OWIxZjc1MzFiMmRiMDFlYjkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQzNzliYWFjZWUyYzhkNjgzNjg4ZjMwOTg1YTkyOTU0NmZiNjU2NmI5MGE5MjI3OGYxNjZmMTBjYThmOTJlZjMifQ" + } + ] + }, + { + "id": "claim_6a12c7f1f2174723b4b6840b8997f8fe", + "statement": "Broadcast is covered by engine unit tests in src/broadcast/mod.rs using fake CommandRunner implementations (bounded peak concurrency, one terminal event per task, cancel marks everything terminal, failing runner never panics, reducer order/terminal-row invariants, failures-first ordering, per-task secret threading), app wiring tests in src/app/tests/broadcast.rs (wizard group/tag flow, refusal while live, tick fold + audit exactly once with correct status/note, cancel flag, themed panel render through tui::render), and an offline e2e in tests/e2e/broadcast.rs that drives the real key handlers to the [y]/[e]/[N] barrier and deliberately stops before `y`, which would spawn real ssh.", + "evidence": [ + { + "resource": "repo://src/app/tests/broadcast.rs#L80-L270", + "version": "repo-lines-v1:sha256:a6e2241dfdd723503e67f4227ce5d9f52718fa2e037180571e599fccb58861cc:eyJzZWxlY3RlZExpbmVDb3VudCI6MTkxLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiJkMmNkNGRiNDhhMzUwZThmMGI0Nzk0NGM0NmUzZmZkYmJhMTMyZDIyOTBhOTFkNzAxMmI1NmViNDZjOGFiNGY2IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0MTJjYTM0NWNjZjc1YmY5YzA4MDZiY2U2OTViZThkZTgwOGI3OTk4NDI1MWE3YTU0ZDIwMmNmNjEwMWRkNDUxIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJlZTYzNWVjYWE1YzdhNzRhMDQzOGJlNTAyZjc0YThkMjIwMGFjOWY2NWQ4ZGE5N2M3MjY4NTMyYjk5MWQzMDI1IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiJhODkwZGEyMGI0MTA5ZGY3ZGI1ZTBlYWRhZDY5MmJlYzU5YTUwODQwODYwNjdkZTgwNGRmNmY1ZWY5MzY1YjI3In0" + }, + { + "resource": "repo://src/broadcast/mod.rs#L493-L998", + "version": "repo-lines-v1:sha256:1a1fceeb4d3d439c939be2914339bf30a01e1e880445951ff7b3453cf7f0b94e:eyJzZWxlY3RlZExpbmVDb3VudCI6NTA2LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiIyNWNmNWZjMjNmMmE2ZDIzYzNlYTUxZTcyZjVlODk2Yzc1NTVmYmFhMDVjZDViMWVhMTgyYmY5NjE4YThlODFiIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0MTJjYTM0NWNjZjc1YmY5YzA4MDZiY2U2OTViZThkZTgwOGI3OTk4NDI1MWE3YTU0ZDIwMmNmNjEwMWRkNDUxIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJjYWU4ZjA5NWE0MTUwNDFlZWU0YjdhZGFjZDUzODI0ZWEyYzQzNWVlZjc1MzBkMzk2ZGNmNWNiODRhNjRlYmE3IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MCwiZm9sbG93aW5nQ29udGV4dEhhc2giOiJlM2IwYzQ0Mjk4ZmMxYzE0OWFmYmY0Yzg5OTZmYjkyNDI3YWU0MWU0NjQ5YjkzNGNhNDk1OTkxYjc4NTJiODU1In0" + }, + { + "resource": "repo://tests/e2e/broadcast.rs#L1-L170", + "version": "repo-lines-v1:sha256:99991554470c401d8ae979119c64b0f5628dc3523701ad3bdb233d7c46fb304c:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcwLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiJjZmI2YjQzNGM0ZDY0OTliNDQxMTlkNWNmYjhhZGI2MWZiNGJmMGNmZjVhYTZjN2FkNDc1Mjc0ZDYwMTRkMTY5IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0MTJjYTM0NWNjZjc1YmY5YzA4MDZiY2U2OTViZThkZTgwOGI3OTk4NDI1MWE3YTU0ZDIwMmNmNjEwMWRkNDUxIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MCwicHJlY2VkaW5nQ29udGV4dEhhc2giOiJlM2IwYzQ0Mjk4ZmMxYzE0OWFmYmY0Yzg5OTZmYjkyNDI3YWU0MWU0NjQ5YjkzNGNhNDk1OTkxYjc4NTJiODU1IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MCwiZm9sbG93aW5nQ29udGV4dEhhc2giOiJlM2IwYzQ0Mjk4ZmMxYzE0OWFmYmY0Yzg5OTZmYjkyNDI3YWU0MWU0NjQ5YjkzNGNhNDk1OTkxYjc4NTJiODU1In0" + } + ] + } + ], + "verification": { + "by": "openwiki/0.5.1", + "at": "2026-09-11T12:46:43.817Z" + } +} diff --git a/openwiki/.claims/workflows/cli.json b/openwiki/.claims/workflows/cli.json new file mode 100644 index 00000000..02c03314 --- /dev/null +++ b/openwiki/.claims/workflows/cli.json @@ -0,0 +1,614 @@ +{ + "schemaVersion": 1, + "pageVersion": "sha256:ea371a884989f7555bb2f101dd7d86a3dd2752545f1af09ecc851be3c182bb8c", + "claims": [ + { + "id": "claim_a2d80e9ee15348a284bc6d5cc4d909f8", + "statement": "main.rs dispatches in a fixed order: askpass re-exec (SSHUB_ASKPASS_FILE set means emit the staged secret and exit before parsing argv) → profile flags (--profile/--manage-profiles, with everything after the first -- split off and re-appended so a remote command's own flags are not swallowed) → db (handled directly) → theme (before bootstrap) → is_subcommand string check leading to CliContext::bootstrap + run_subcommand → global --help/--version, unknown positional exit 2, --dry-run, then the TUI.", + "evidence": [ + { + "resource": "repo://src/cli/mod.rs#L21-L78", + "version": "repo-lines-v1:sha256:a754c3b2e7c136b2453091f2e5cc9c4520edcdc7eadea866eb1e9e4c14fafa07:eyJzZWxlY3RlZExpbmVDb3VudCI6NTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjdmNWYxZWU0MWEzNmQyZTRjZDNkOGIyNzdjNWY4YTQyM2U2MzhmYzYyM2Y2NTA1OWY2NWE0NDRlNWI2MzMyMzAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzZDhiOWM0YWVkYWYwNDZkZTU3N2M5NzFlOTc3YjlmNTkyNjdiZDdlYTk0NjJmMDY4MDFjNTdmNTlhM2RhNDMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImY2NjcwNGYxNTJlN2EwOGQ1ZTA0YjcyM2M3MmY2NjE5OTZmZmU2NmJjYzFiYTg0YjQ1YjBmMTZkYjMxN2E5OGIifQ" + }, + { + "resource": "repo://src/main.rs#L8-L50", + "version": "repo-lines-v1:sha256:acf87b01a988358de96ce717f85f405e132398a7d840b4a2aa4e843174da23e8:eyJzZWxlY3RlZExpbmVDb3VudCI6NDMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNjZDRkNjlkMTEwNDNjNmNkOTNjNGJjNmE5YWQ1YjRlODExZDgyNzdkOTI0NTVlZTZkMWU2NjY0MGU5ZWI1M2UiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImJiYWI4NjRhZTJmNWMxOGRhOWQxNWRlZjAwYmVmOWIxZmQ1OTIxYzhmZTliODQ5OGUwYTRkY2E4Mzc4MTQ0ZGIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImE1Y2VkMThlMWY2Njc4MDk3YTE5Njc5ZTU0YjBiNWJmMWQxZWExYjE1Yjk1ZTQ4YWZkMTU5YzMxMTg4MDI2ZmEifQ" + }, + { + "resource": "repo://src/main.rs#L92-L98", + "version": "repo-lines-v1:sha256:353833955b08ef6e38cf34aec673d1eb084b3aba9d7f30483ad7dd9921e7ef25:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNjE5M2Y1NGY4OWRiNjU2MzI3NDJjZjFjZGFjMWZjOGIxMTMxMDViYTdmODM0YTE5OTg5MDU4YjVmYmJjZDZhYSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMTllNzYzNTVjZjExOTk0ODBjM2Y5OTgzNmIxNDg2Yzg0MWQ4OTFmYWFkOTA0YzYwOGUwOTMwMjJkMzc1N2FlZSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMzljYjRmOWU1MmJhNzRmZTQ1OTllZjc5ODk0MTFhYTIzMWZlMWQ3Zjg0MWViYjMyOWFlNDEyZWVhNDg4NWRhNCJ9" + }, + { + "resource": "repo://src/session/askpass.rs#L103-L119", + "version": "repo-lines-v1:sha256:5526c149c27da24f52f71b5f3b0c84b8a372db5d3101cef1755df35326b7ae14:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFkNmY0MWNhNzg2MDBhMjBkMGRiZTI1YzE4Y2I0ZTVjMGNiMjE0NmYwMjkxMDM3MTRkZjk2Y2NhNDcxNDM3NWQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjdiYzk4ZDZiMGRjNDkyYzUwMTZkZjEwODA1YTNhNTRlYjljMTg0YTVhNjEzN2U3ZjRiZWNmYTBjMjIwNTUwZDAifQ" + } + ] + }, + { + "id": "claim_768bb418833448e38208f210467f82b8", + "statement": "Headless commands resolve the profile non-interactively (never the picker): --profile NAME selects directly and an unknown name fails with the available list; without it the last-used profile is used; --profile combined with --manage-profiles or used in compatibility mode (SSHUB_DATA_DIR/SSHUB_CONFIG_DIR overrides) is rejected.", + "evidence": [ + { + "resource": "repo://src/main.rs#L80-L90", + "version": "repo-lines-v1:sha256:c740835ce3fe217a9105c0ccc0581dd01e2c903791309f85b7941ff205d3a743:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImZjNDFlM2I0MmI2NWE0OThjZWY2Yzc2Njc0NjQ3YjhkYjFkYTQ1ZWRhM2NmZWU4MDVkZDA4NDZmYjI5NDM4MWYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImVhZWE5MmM5ZDdlYWQ0ODY3NDg1MTUzNmYxODRiYzBlZTkwOWJmODBlMTNiMGI0MDk4NzA3MGJhNTRhYjMyMWIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImFjM2NjOGJjZDg5YzFlOGU5ZTMwNjAxZjhmMTQyMmMyZTllMThiYzFhOGIwOTcxYzBkYjczYWE2YWRhYzE3YzEifQ" + }, + { + "resource": "repo://src/profile/mod.rs#L354-L389", + "version": "repo-lines-v1:sha256:1eadf60b759b6100387a422aad2bc60139462e1653b863f98e9dfc5e42e1cb4f:eyJzZWxlY3RlZExpbmVDb3VudCI6MzYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk4NDVkZTllMmQwNDFkYmEwNTk1N2FiMmQzNjVjNjM0NzZkNjQ5MDM5MWVkNDY2NzhiZjQ1NTIxNTAyZWIwNTQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImIwMThjZmIwM2VjNjVlY2UxNjk5ZjVjOTRiNjkwZTNhZDI0MzFiM2RkOWNhMzJjNDY1YjNhMzI5ZDUxMWM5ZmQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjIzNTk1ODBjNzdjZmZjOWYwNGFiOGM4ZTZlOTY4YmI4NzBhOGY2ODNmNDI3NGZmMmUxYjg1M2ZiMGU2NDM4ZTYifQ" + }, + { + "resource": "repo://src/profile/mod.rs#L411-L473", + "version": "repo-lines-v1:sha256:44712e8912c9f9e8916c7a75bf1c7beb4260b5a91248fb5861cd495b00dde008:eyJzZWxlY3RlZExpbmVDb3VudCI6NjMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjAwMjY1MDU1NDk1MTQwOGU0ZGIwZjRiNDgyM2QxNTljNDUxZGMyYzlmYTJmYzIxOTcyMmM0MzdhOGI0YTdkODUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQyNDY1ODA1NjE3NmRjMTU2ZDljNDQ4MmYyZGIzY2EyMmY0ZGE1NWVkMjFiMzM2ODM2OWMxMzY1N2ViMjk3ZTgifQ" + }, + { + "resource": "repo://tests/smoke/cli_commands.rs#L894-L914", + "version": "repo-lines-v1:sha256:c712800130c4bd0c7bc27d2191ccc8b90d5bc98efefa6249a95b8e9cfd9d6085:eyJzZWxlY3RlZExpbmVDb3VudCI6MjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM3NjY4MWQ2OWFlY2QxMGExNWY4MzFmNWU4NThkMWNhMTZkYWVhMDhlMjhkMjFkNDg0NjRhOWU1ZDU4ODM2MzUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE3MTEyYjY1NjQ0Y2RhM2I4ZDllOTVhYWEyZWZkMzc5ZGJkNTY3ZjUwMDYwZGI0MTAyZmQ5ZTE2NzliNTdkYzMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjkzOTI1YTkyOTE3OTkzYjk1YTk2ZTU3MWFjOGFmOTMzYTJmNDU1MGVkNjgzNjkyZWE5YzczNDc0MTFhNjQzM2MifQ" + } + ] + }, + { + "id": "claim_deed7f36dfa2481eab39c0720b33a55a", + "statement": "CliContext::bootstrap loads the profile config, opens both databases (metadata.db + launcher.db), builds the SshConfigResolver, picks a namespaced password store (OS keyring when available with FilePasswordStore fallback and credential migration), and loads merged hosts — so every CLI subcommand except theme shares the TUI's state; run_subcommand never bootstraps, main.rs owns that step.", + "evidence": [ + { + "resource": "repo://src/cli/context.rs#L32-L80", + "version": "repo-lines-v1:sha256:e2eb13130b4a4295c89cbfa1189cd96e9db2e3b556fe68f7ba98ba6176b5bed7:eyJzZWxlY3RlZExpbmVDb3VudCI6NDksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU2OTQwM2M3Yjc1MGI0ZTgyODcyNzc4ZWRjYWE4MzdhNGZjNWQ3NDU3ZDkxZDU1ODlmOGQ4OTI0NzY1MWE4Y2MiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjllZDllMWRkZDhlZGFlNjJhMTQzYWVhMzdlYjFmM2Q2YzMyN2E3MjUzNTMxMGU4NTc3ZTg5NjZiMDgyNTgzN2IiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQ4ZTFlNWQ5N2EzZjFkZDNjNDE1MzIyMzEwYTM0OTY1YzgyZTg0ZWY4M2JhNTQxN2IzYjMxMjk4ZjExNGMyYTIifQ" + }, + { + "resource": "repo://src/cli/mod.rs#L44-L46", + "version": "repo-lines-v1:sha256:ffb93260dca0b38374b48563bb98afc93179d3be87a2014e4fb94bce31e1f958:eyJzZWxlY3RlZExpbmVDb3VudCI6MywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNzNlNWNhYWExMWI0NGViOTM0ODVmMzJjZDA0ZTU0ZmRmN2VhNzE0NDA3M2Q1N2FjNDc4YzYyNDcwYzE3ZGMzZiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZDkxNGRlNzk5NWNjNWZjNzZlMzRmN2M5NTgzNzJhZTk5YzliZDRkZWUxMWQ4ZDVmNDViYTg5ZmNkZTk0NjY4NCIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZTk2ZjhjYjA4M2ZhODAwYTRkY2QyOTY5NzIyZDdiOTI2ODZhZGQ5ZWJlMzY1NTQ5MmZhNjcyYjFhN2NlZDI0MiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiYWMwYTg0ODI3OThjNWNlMmVkZTU3Y2JmNzI1MTZkYjk4MDRlODM4NzY0MmE5NmQ2MzIzMGVlOWQ0MGY4ODE4NyJ9" + } + ] + }, + { + "id": "claim_3e109b60cbff4af1b6e7c21577be073c", + "statement": "CLI conventions: --format plain|json on listing/show commands (plain default, parsed by scanning the whole arg list); exit codes 0 success / 1 operational failure / 2 usage or bad flags via parse::usage / 124 for exec --timeout, with child ssh exit codes propagated from host connect and exec (ssh's own failures keep 255); destructive commands refuse without --yes (exit 1) and db purge requires --yes-i-am-stupid; an unknown positional first arg exits 2 instead of launching the TUI.", + "evidence": [ + { + "resource": "repo://README.md#L170-L175", + "version": "repo-lines-v1:sha256:3217fe3d6a0c86cd7bf30b700f78307423c157dbc59c2533f700adf1793db587:eyJzZWxlY3RlZExpbmVDb3VudCI6NiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNjAwMmU5ZDY3YjUwMWIyNjIzM2FmZTM4YThiYTRkNDM0ZjQ4YTRhZGIwNTI2NjJhOTJiZjEyMTUzNGM2NDBjNyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZDM5NGYzMDJhNTg5NWE5NjRlMzBiOWM1Zjg2OGNkY2NiNzRkZGEwMmU4MDVkZjAyYmNkNjlkMGE0NDcyMDI0ZCIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiY2M4NDdkNGEyZWQxY2FjYTQ1YzBlM2VhZGYyYjcwZWIyNjI0NzVmMjA5YmQzNTliNjM1YmUxMmNiNThmODdjNyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZTcyNTAwYjU0ZTQyMGJmYWIyODU3YzMwYmE0ZjczNWFiNzM5Yjg3YWQyMzQyODgwZTQ3ODEzZTU5NWUwZjVjZiJ9" + }, + { + "resource": "repo://man/sshub.1#L348-L364", + "version": "repo-lines-v1:sha256:8b7c5a975aadc65f6044febdc68fdbbcfb2e12421baab573092ca6c8a6d8a5ce:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBmMWJmNjlhYjYxYmE2MjRmYTc4OGU2NzlhZDg1YzEyMjIzMjg5ZWVmMGE4NDRjMWViZmJkMTUwZTE4MjQ4MTEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM1ZGVhNTBjZTBhZTBkYWFmMmVmYWMwZDQxMmMzNWRkN2Y2Zjg0Nzk0N2RkYmVjZTBmOGNhNjgwY2EwMWZiNmYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjZkM2EzOGUzYTI1ZGZkMmM4MTUyOWZjZmY3NDRhYWE3Y2M4NjQ5MjhhZTVjNThmNWI3NDE2MjVmYmI1MmY0MDUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhmODQ0ZjRhOGVjYzMyMzkxZmU1NTViNWJiN2ZjZjMyN2YwMjlkZTMxZmNlNjY2NTBjOTVkMGNmYjk4YzU3Y2EifQ" + }, + { + "resource": "repo://src/cli/parse.rs#L105-L117", + "version": "repo-lines-v1:sha256:e603518ae1d618f0525c416cd359afc7616cbd80b8308b85fa00d314147318bd:eyJzZWxlY3RlZExpbmVDb3VudCI6MTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI5MDUyNWUxZGVhOTkyNTY2NDZjNzFmMzM5NjBhM2E1NjZhMzk0YjU1ZjZiMGQ3NTU2NzY5YTgxZmNiZGI1ZmUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM4MTI5MWU4ZDU0NWU5YTE0NGEwYmI0NzVjZTk3YTRiOTBjOTEwMzE0ZTFlZTZjZWNmMzk0N2MxMzgyOGU3OGYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjVmNTA4ZTAwMTc5YjMzZTIwM2ZmMTYzZTU2ZmM1ODJhOWZhOGUyYTU0ZjQ0MWM4ODM3ZWJmNWQ5MTgxMTFhZTMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEifQ" + }, + { + "resource": "repo://src/cli/parse.rs#L12-L30", + "version": "repo-lines-v1:sha256:eddf7fd29e03ebd3c648e58634bae191645c485f7de8b0990ae75a75ae64784c:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI5MDQzYmQ1NmQwM2MxZWZmNmY1MDI2OTUzMDM1NDdlMzM3MDYxNGRiNjdmNzE1OTY2OGJkOWFjNjEwOWNhNjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImY1NmFkNTc3Njc3NTc5ZDQ1YmNmZjIyNGNjMmY4NGFiNWQ0ZTY4MjYzNzE0NjkzZTE1NGU0ZWI2NWM2MjEzZmIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjliYTgwYjE4NWM0NjI2YTk2NmJhYmJlNjY5ODVhNDlkZjQzMTNkYjk5MjFmOWNlNjc1OGI2MTg0MjJmODdjOGQifQ" + }, + { + "resource": "repo://src/cli/theme.rs#L35-L39", + "version": "repo-lines-v1:sha256:6d46d86faeae35aa272b0653317d46a6a2012d906cbc35900365333782135f8d:eyJzZWxlY3RlZExpbmVDb3VudCI6NSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiOTAxODNkZGY0NjliYjg2ZGQ0ZWE1ZmYyYjVkYzk0ODAyYTVhMWM3YTBiMjgwZmY1YTJjNjQzMzc1ZDQwM2NlYiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZmRiYTEwNjM2MmFmMWJmMWE4M2Q0OGZlYTU2ODU4N2M5ZjhhYTU1YzMzY2Q2Y2FiN2IyMjQyN2YyNTUyMzJhNyIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiOTY4ZjI5ZjgzMjE2MWI1YWQ5MjZjYzEzNjcxYTIwMTZjODlmNzZlYWMyMzQ2YzdlMTc0YTE5ODk3ZDYzZTI4MSJ9" + } + ] + }, + { + "id": "claim_b7b9d3b92b0442ed857b01407475eaa6", + "statement": "The subcommand surface is: host (list show connect resolve search add edit rename delete duplicate; aliases list and connect), exec, group (alias groups forwarding flags), identity (list show add edit delete agent-remove), tunnel (list show create start stop delete), sftp (ls get put rm mkdir rename chmod), audit (list stats), tags, theme (check list show), import/sync/export, completions (bash zsh fish), and db purge — with is_subcommand covering the database-backed set and db/theme dispatched separately in main.rs.", + "evidence": [ + { + "resource": "repo://src/cli/host.rs#L28-L51", + "version": "repo-lines-v1:sha256:bee48ebde354f1f94c59ab38923014ec95f57ee15601184d27bacb3c58c410c6:eyJzZWxlY3RlZExpbmVDb3VudCI6MjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRmNjA3ZmUyM2ZlZmUxOGZkMWFmMDI1MWJkZjNlNGRlYWY4MDI0NGI0MjAwMzljMDZmYTVmNjRjMWQ1Mzk5YzQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQyZWQ0YmQ5YjZjM2Y2MjhmNTg4NTExYTliNGI4NTdmMTFhYzU1YzViZGU0OTA1NDk5MmMyOWM3NGViNGJjZTMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjRlMjNhNWFmMjlkNTMxNDM2NTExZGY3YWZiMjliMTAzOGU3MzM4OTQxYTYzMjdmZGIyOWI0NjE2NjE5MDk1MGQifQ" + }, + { + "resource": "repo://src/cli/mod.rs#L23-L78", + "version": "repo-lines-v1:sha256:13bfed6f863ae0981f30b5941098322e4f63933c4f107076bcab9c7290ee14cb:eyJzZWxlY3RlZExpbmVDb3VudCI6NTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNmMDQzNzliOTdmYTc2Mzg1M2MyNDY4MGUwNzBkYmU3YWNiNTY3YmRjYWViOTNiZDU0M2YwYmU0YjEyNGRiZTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImFhNmVmZmU0MGM2NjQzMzdjYTI2ZWUwYjkxODI4OWE1NTE1NTQ0MDViOTQ4YzE3ZDA1YzUyYzkyNTNlNGYwMTciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImY2NjcwNGYxNTJlN2EwOGQ1ZTA0YjcyM2M3MmY2NjE5OTZmZmU2NmJjYzFiYTg0YjQ1YjBmMTZkYjMxN2E5OGIifQ" + }, + { + "resource": "repo://src/cli/sftp.rs#L21-L38", + "version": "repo-lines-v1:sha256:1f08d425f1e118371cb0bcd02d5c52afae56b1cdc50246e7aa62d23b53be486f:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImUzY2Y2MDA3Yzg0ZDBhYjg3ZGRjZGQ2NDk1YzcyNGU5YjUyNTA5Nzg4MmNiMTg2NmQ3YTkzZGJkZTQ2YThkNWYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImVlOTI0ZWJhNWQ1OWFkYTBiNDg1MjA0NmI4ZGFhZmYwY2NhNTM5Mjk0ZDJmMTI5ZThmYTJmN2I4NTQ5ZDQ2NTciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijg2NDA1MjFlNWY2MTAwODliYzJhN2IxNDVlMDg5Y2RkNmIxM2M0NjcwOGE3NDA5ODAxZDlhOGQ3MzlmZmE5Y2UifQ" + }, + { + "resource": "repo://src/main.rs#L171-L221", + "version": "repo-lines-v1:sha256:2a2a3a28ff5aa2ce321fab2186ae72a14c911d20f60e77764cc52f7e7ee920a3:eyJzZWxlY3RlZExpbmVDb3VudCI6NTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjliODM2MDA3MWQzNDQ5YWZkYTlmOGI2NDUzZjYxZjI0ODA1NTZjNzVmOThlM2VmYjRmYjlmMGRmOWY3ODI3OTQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImNhODBlM2Q2MGY3OTBmZGRmNGY0OGYyZWIxMmJiMjkyOTJhYzAxOGIxNzFkYTA1ODc0ZmQzZTIzYTQyZGNkZjAiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjgwZjNkNDNkOTBmZWUzM2ZlOTdmMjUyNjI5YjdjYTc4MDA0Mjk2MTY0YmQwNTAzYjc0N2MzODRlYzkyMmU4MzYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImJjYjllMTViN2QyMDJlNmIzN2ZiMWRkNTJjYTg3ZjMwNzgxNWNiNTUyOGMwYjNmYzMzYWI3YzQ0ZmE4Mjg2ZjYifQ" + } + ] + }, + { + "id": "claim_11a4daa1b8d34584bce74eaa350ca052", + "statement": "host connect spawns ssh/mosh as a foreground child with inherited stdio (Command::spawn + child.wait), propagating the child exit code, updating last_connected, and auditing via=direct/tunnel/agent; it never enters the TUI PTY session module (Session::spawn) — a transcript exists only when session logging wraps the argv in script(1), and a missing transport binary is caught by a best-effort `which` pre-flight that fails open.", + "evidence": [ + { + "resource": "repo://src/app/util.rs#L183-L228", + "version": "repo-lines-v1:sha256:4048affb87f9647f8ff4ed3804089a41b3b5359a6e343c696610b33a6f75dab1:eyJzZWxlY3RlZExpbmVDb3VudCI6NDYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjcwZmEyYzQ3M2FkYzFlZTMyODYwNzEwZDI0OWNlMTU1YzQ5N2IwYzc5Y2QwZDAxMTNlMGY3ODk2MTRlODY0NzMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjliMjY0ODIzZjRlYTUwOGQ5NjNiMzkwMTI5MGEzNzQ2ZTYwZDY2ZWEyZDdkMTZhNTdiOGUyYWFkMjBlNzY2Y2MifQ" + }, + { + "resource": "repo://src/cli/host.rs#L140-L282", + "version": "repo-lines-v1:sha256:08768172fe5209aceb6218d5a187a6897414a90d97d5522283129f6170767244:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQzLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI3OGU0NmFjY2ZkZGVlNTZkMmFiODk3OTIyN2RhYmNjYjc2Mzk2M2VhOTNiZDVkYTczNmU4M2RlMmM4YmZiYjk4IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0MTJjYTM0NWNjZjc1YmY5YzA4MDZiY2U2OTViZThkZTgwOGI3OTk4NDI1MWE3YTU0ZDIwMmNmNjEwMWRkNDUxIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiIzY2RiYzAzYmMxM2U4MDIyZDMzMDc2ZjA4MmYwZTIwZmE0N2JhMWNlNjkxNmM4YTZhZjQ0ZDMxNWNkZDQ3MzI4IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiJkMzg1NmExNDVhMTc3YzJjZTMwNjYzZGY3YTVmMmY5MTUzYTExNDc1NGExMmYwMzExNTFlOGNiZDMyNjcxMTdiIn0" + }, + { + "resource": "repo://src/cli/host.rs#L618-L640", + "version": "repo-lines-v1:sha256:d0035ffbd721936735c285036b24bb6dfcc21771a9d03abc3464528598b502a0:eyJzZWxlY3RlZExpbmVDb3VudCI6MjMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjUwMTc5ZWY5NWY2MWFhNjBiMGYyY2M1YTE4NWY0MWE4YjhiMWJmZDYwYzg5NDVjY2YzZWNmYjc1NTkyMjBmOTgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBhNzYzODM2Y2I2NTM4NDU3MTk4ZWExZWZiOWNlMTBhZGM2Mzg3NmI1NGE4N2E1NTRmMDVhOGI4YmViYTc3MTAiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNjZGJjMDNiYzEzZTgwMjJkMzMwNzZmMDgyZjBlMjBmYTQ3YmExY2U2OTE2YzhhNmFmNDRkMzE1Y2RkNDczMjgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRmMzZlN2ZhNmMzZjMzYTVmOWY0YzgxOGEzMWU1ZDBhODIxODMzNDk2YWZiZTVjNjY4ZGFlODBlYjNiNDAxZWUifQ" + } + ] + }, + { + "id": "claim_407412ac8c4840ed86d8f70045504add", + "statement": "exec reuses the connect argv (session_argv_for_entry + prepare_cli_connect_argv, so stored identity, credential and ProxyJump apply) and then transforms it: -T or -tt for the PTY policy, -o RemoteCommand=none to strip a stored per-host remote command (truncated from the argv tail) and clear an ssh_config-level RemoteCommand that would otherwise make ssh refuse the run, and -o BatchMode=yes only when no secret is staged (BatchMode would disable the askpass helper).", + "evidence": [ + { + "resource": "repo://src/app/util.rs#L184-L203", + "version": "repo-lines-v1:sha256:3962185bdbd55a7cfee44ad7083ead954c61be4e31d17ebaf04bc3c5f22d01f8:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjVmZDA2MGNlYmMzMDg1MjE1N2FlZWVmOTc4ZWUwMmQ3MjRiYWNiZjJjNmU1MWYzNTUzYzA5NWRkZDY5MzU0ZTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjkxYzk0Yjc2OTYwNjA3NWU1NTNlY2Q0NTZlYTQ4YmFmYTYzNWU1NjUyYjhmODkwZmNjNjBiODhkMGJlNDVkMjQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhmNjdkZjM2MTEwZmJlOGIxZDRlNjk4N2Q3N2Y4NzYxMDJlYzgwMTZlOTBkYzkyMDVlMGFiZGNlYTI2ODUyYjgifQ" + }, + { + "resource": "repo://src/cli/exec.rs#L281-L304", + "version": "repo-lines-v1:sha256:46c43acd77190bde44bfc486fc648b5d9e9f1f4e541c3582a386d214a42b5ae0:eyJzZWxlY3RlZExpbmVDb3VudCI6MjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU5NWEwMWM0YTFmN2VhZjc1N2Y4YTIwMTdiMzJmOGUxMThiNjY5MmU5ZjY1ODFmZmI3OTBjM2NiZDJjNzVjMDQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjBkYzNiMDQxZjA4ODQzNGYxNDE0YjhmNzYyZTdiMTliZWM0YjE5MjY0YTUyZDY0ZDU0MWZmMDdkMzNlZmM2NDYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjViMjgzMzZkMjZmYzAzMjY5ZTQwOTkxODIwYTE2MzM3YTIzOGJmMTM4YzE5YTI5YjlmZjcyYTBhZTUwN2Q2OWYifQ" + }, + { + "resource": "repo://src/cli/exec.rs#L41-L115", + "version": "repo-lines-v1:sha256:39912478576737a26992af3f198519d91fcb2f33256fcc836ffa11a346716d8e:eyJzZWxlY3RlZExpbmVDb3VudCI6NzUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImUzY2Y2MDA3Yzg0ZDBhYjg3ZGRjZGQ2NDk1YzcyNGU5YjUyNTA5Nzg4MmNiMTg2NmQ3YTkzZGJkZTQ2YThkNWYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijg0ZTQyMDhjZDZmZmI4YzRhMzU0N2IyZjU0YWRhMTk5NzhjM2YzNjVlNzhmOTUwMmVmNTE0NWNkZDJhYTg1NDMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjY5ZDQ5YmVjYmU3MDc3MGYyYTdmM2Q4ZWQ5MzYwYmQzMjJjNTliNTUwY2RiNDc5YTViMzdhZGJlNjg0Y2E4ZmEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI4YTliNDhhZDE1MGMwMzhkYTViOTYzZDczM2YzN2Y5YjEyYmMwZTZiMzU2ZTU3OWRlNmE0NzlkYzlhMGY3ZGQifQ" + } + ] + }, + { + "id": "claim_f2388473dbf24e039762621c60127aab", + "statement": "exec's operational contract: stdio passes through with stdin inherited in both formats; --timeout spawns the run into its own process group (only when a timeout was requested, else Ctrl-C would not reach ssh) and SIGKILLs the group at the deadline for exit 124, which is required because a ProxyJump ssh -W helper inheriting the pipes would otherwise keep the timeout from returning; --format json buffers {host, command, exit_code, stdout, stderr, duration_ms} with pipes drained on their own threads and a record emitted even on spawn failure; no session transcript; mosh hosts are refused; the run is audited via exec without the command string.", + "evidence": [ + { + "resource": "repo://CHANGELOG.md#L209-L227", + "version": "repo-lines-v1:sha256:63b7d6e79b78c919f2a1a3aa68df9fc1781da4543f0379799b7abc86f2d8c4de:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImIyMDIxMjY4ODQwNjcxNzJhODUxMmU4ZmQ0ZDUwMDUxZTRkZjg0MDg1ZmU5ZjFhOTljYzAyZDliODVlMDdmZTYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFkMGQ0OTc5Y2JlMzFlZTc4YTkxYmQ0NWM4YTIxZTRlYTU4OWI0OGI5NDEyYzVjN2Q5ZDY5OThiYzcwM2M5YzIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjU5ZmU0NWFkNjk5YzU0MTQxOTAzZWFhMDQ2MGY0MDY5YjBmYTM4NTMzNzQxZmYzNGUzNmEyNzVmMDc3MWY2OTYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjA1ODQ4YzcxM2JmYzE2ZTcxMjgxMjA3MTU2YmM5YjQyNTU1NjBmZTQ5NGYwZTE4ODYzNTUwMjgyMDk3NzZlOTIifQ" + }, + { + "resource": "repo://src/cli/exec.rs#L128-L244", + "version": "repo-lines-v1:sha256:adc951ea76da1e330f30188e28c8607a95f8070a0cdc23768c2f76acd26ffb84:eyJzZWxlY3RlZExpbmVDb3VudCI6MTE3LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiJjZWU1MDUyMzE0Mzk4MDc2Y2MzY2I1ZDRhMTUzMmViNzg1NzUwMGU5MDRjOTQ2MDYzNTgzMjA5OGE4MDE5NDZiIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI1N2IwYmEwMmU1ODI0ZTIyMTRkMzQ2NTM4OTg1MzdhNDJkNGIxZjNmOThiNzE0MWMwNjZhMWI0MGIyMDdhOGFmIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiI5OTliZGVmZWVhY2E2MzQwZTAyOTBkYzg2ZTg4OWM4OWQ1ZTE4OWFhNWJhNDRiNTg1YjNiNzA3MzRmNGE2MmQ4IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiJmZDE0MDZlNzI3YTQ0N2FjOTI1YTEyMzFhOWVjOTg2NDYzYjFkYTg0MmM0Mzc0ZjhjNThhNzBlYWU3OWVhMGJlIn0" + }, + { + "resource": "repo://src/cli/exec.rs#L306-L374", + "version": "repo-lines-v1:sha256:86293888ece47183809fd0ced5e1a11043604bf27a3160cf2f2de92f2ce6402c:eyJzZWxlY3RlZExpbmVDb3VudCI6NjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImYwNzQyZDJkYWE1NjJmOTMxMTllZGRkMjBjZjkzOGY3Y2ZhNjdmMmVjNjFhMGEwMjMyNTU3YWY0NzdmOWI5OTgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImVmYzQyMzc3MTcxZmNiZmVmYzMwZGJkNjhmOWM2NDIzMjQ1NjY3NjZmMjdkYjljMWNlMjQxNjQ2NjNiOWM1MWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjdiYzk4ZDZiMGRjNDkyYzUwMTZkZjEwODA1YTNhNTRlYjljMTg0YTVhNjEzN2U3ZjRiZWNmYTBjMjIwNTUwZDAifQ" + }, + { + "resource": "repo://src/cli/help.rs#L66-L96", + "version": "repo-lines-v1:sha256:8deb8f7e92b9bc5f1a9758eb1cf8ebb1dc39f409c32a2cd4ec2a0f03add5037d:eyJzZWxlY3RlZExpbmVDb3VudCI6MzEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImMzNGY5OWQzZTRiNjUyNDQ2ZGFkMDhlMTc3NzUzNjdhNGVmZTZjMzA2MjRjOTM4YzhlMzZmNDc3ZjUyN2ZlNDMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE1OTM3Njc5MWU2OTQ3NmY3YTA3MTg5NmRjMjViOTEzN2VmODQyM2UzZTk2MzQwY2NmYWMzMzIzZDQyY2E1MWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjNhYzMxMzU1OWJhNjcyMDA0ODYwYzdlODJhOGY0ZGQ1M2RkYWE3ZjIzMjJmMjBhMWQ4YjU2ZDdmMzQ4ZTRjZmQifQ" + } + ] + }, + { + "id": "claim_45fe0cec950741f0bf83b30c32b3f1ae", + "statement": "Everything after the first -- in an exec invocation becomes the remote command joined with spaces exactly as ssh does, so a bare shell operator splits locally (sshub exec web -- ls && uptime runs ls remotely and uptime at home) while the quoted form -- 'ls && uptime' survives as one argv word and runs remotely; only the part before -- is scanned for exec's own --help, and profile-flag extraction in main.rs holds the tail back too.", + "evidence": [ + { + "resource": "repo://src/cli/exec.rs#L247-L265", + "version": "repo-lines-v1:sha256:cd568783d5308c576c9b468809582309829bc34a8ca260fad7f29f596ebc90aa:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI0MzkyZGZiNDJhZTdkYWQzZjYzMTg5OTdjMjA5OGYxYTJmNzIxNTAzYjQxY2EzMzkzNzg0NjdjMWI4NWE5YWEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImViYjJjNjZhYjY5Y2VkOWE5ZTIyOTI0OTY5MTg3ZjY4ODhmNWE0NmYyMWNiMDIwZjc0MDFmZGQ1MDc2MWU3ZjAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjEwMTNiNzA3NWMyZDkxMjcxNGEzYTRjZGMyYTNhNGQ1Njc2ZWI3ZjU5M2M3NWNhZjQ1ZmRhOTdlNTI1ZjVkZjIifQ" + }, + { + "resource": "repo://src/cli/mod.rs#L47-L53", + "version": "repo-lines-v1:sha256:896171a9ad41a66664c519235d894328ac6d06826a09303e0ca329f16c99f2be:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYTUyZGI0MTgzNGYxZmFjNzFjOWJlOGQ1ZTVkZmFkNzZjNzhkNTE5ZjYxZThiZDg4YWE4ZDkyZTZhZjNkZDExMCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYWQ5ZTEyNTQ2NDI1NTc2ZDNkZWFlOGM5YjlkYmYxMjhkNWQ2ZGRhZjVkN2ZlODU3OTM0OTI3ZGVmZmEzYjlkZCIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZmZiOTMyNjBkY2EwYjM4Mzc0YjQ4NTYzYmI5OGFmYzkzMTc5ZDNiZTg3YTIwMTRlNGZiOTRiY2UzMWUxZjk1OCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiOTUzODg4ZjA3N2M4ZDgwYjhmNjI5OGYwOWUxMTBlMjU1NDFhNWU1OGZkZTUyNDI1YjA1ZmFkNWIzM2MzMjdjOCJ9" + }, + { + "resource": "repo://src/main.rs#L17-L28", + "version": "repo-lines-v1:sha256:ba2fa26e92d827db0af7428ab9ced6a1b582a06f1598d2318205159c19e099fc:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU2MGUwNGFkMTM0ZTNlOTczMDM0ODc0Yjk3NDQ0Y2Y5NTUzNGMyNjJjYjEwNTRhZWJiMDk3MGVhMThhNTNhNzQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVmYzI1YjVjODY5YTRlY2I1ZGM1YThhOTEyNmFhNmNlN2ZmODlmOGNhZGUzYWQ2YjA4MGU0MDg1NjAwYzdjMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjFmMmU1Mjc3MGQyOGI5NWM2OGY0ZTQwYTkyOWMyNWU0ZTU3YzU2ZDVhYjM2ZjhiOGJlNDUzMzRjMzk3YmFlMTciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJhNjJkMDJmN2NlODIxNWU2OGM5MDk5NGNiMjgyYmRmMWFjNmFiNGU0MjAyMDc5MzYwNjNhNmRjZjY5Y2ZhOTIifQ" + }, + { + "resource": "repo://tests/smoke/cli_commands.rs#L879-L914", + "version": "repo-lines-v1:sha256:7987f7e940fba4d0695d5c4d3e79b08cb7599712f5a716b53d719f5bfe67e014:eyJzZWxlY3RlZExpbmVDb3VudCI6MzYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImYwMmEyMTAwMjIwY2UyYmM3Zjk4ODBkMzUzYWIzZTM3ZGVhZDQ1OGFjZmRhMGZlNjUxNzVlNGM5YTNmOWNiZWIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjdmOWFkNDYzYmU3MWM1ZjQyMDk0NmFmYmYwN2QwZmU5ZjVmMTFkYTgyZjFlOTQ5MDNjYzMzOTVkOGMxYTBiMzgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjkzOTI1YTkyOTE3OTkzYjk1YTk2ZTU3MWFjOGFmOTMzYTJmNDU1MGVkNjgzNjkyZWE5YzczNDc0MTFhNjQzM2MifQ" + } + ] + }, + { + "id": "claim_d0a76d5a71eb4ee89cc232ef4c5309e4", + "statement": "exec treats unknown flags before -- as usage errors (exit 2) rather than letting them vanish into the positional command, and --timeout requires a whole-second value of at least 1 (a flag without a value is an error rather than a silent no-timeout).", + "evidence": [ + { + "resource": "repo://src/cli/exec.rs#L51-L64", + "version": "repo-lines-v1:sha256:4c128b836d6ddc65506bb7e0395ea84d350eee882c9efe1fc29e47fe9cdca808:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijc4ZTgxYjQ3OTc5YTcxOTI1NWM0MmY3OTI0MzJhYjVmYmZmMGI5ZDAzYWNhYzE1ZWExMDc3NjQxNmNmNmVmZmMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFkOWUxMjU0NjQyNTU3NmQzZGVhZThjOWI5ZGJmMTI4ZDVkNmRkYWY1ZDdmZTg1NzkzNDkyN2RlZmZhM2I5ZGQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImIzYmEyZDMwODFhZTU5YzljYmNmZGNhNDcwMWNkMzY0MjRmZGZiNmFlNDIwMzdkZDg1OWUzNTM3MTNkYTEyMDIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImE0YWRkNDYxODliMjIyMjE1ZDdkZDFlY2E3NWQwYjY5MDhmYmE0YmZlMWVmNmI3OGYzYjYwZDBiMTNjZGE1NTAifQ" + }, + { + "resource": "repo://src/cli/exec.rs#L66-L87", + "version": "repo-lines-v1:sha256:a52c214c039da497c2f34ece3494c5d6fab3d9eccb1d12656b5e030356f29ca4:eyJzZWxlY3RlZExpbmVDb3VudCI6MjIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI3ZDllNWNiMjU0NGMzZjE0NDk2NDI3YzA1NjQxZTgyZjJjZmJkNDA1Y2IxYjI4M2E2YmFkYmFjNzAyM2VhM2QiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImJjMWVkZjg2ZmNhMmY0MzE1YjZhMDlmNjA2NzEwM2ZkNDkxZTdhNzU3OTAzNDM3NzI5NWMwYjEzOGRjMjZiZTIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjNjOWVlMmYwNDI5YTk2NDkyNDcxNmQxYmY0MmYxZGZmMGY4MzE4ZWQ4ZmJkZWI0NWYxZWY5ZGY2NDNhZjZjNWUifQ" + }, + { + "resource": "repo://tests/smoke/cli_commands.rs#L801-L892", + "version": "repo-lines-v1:sha256:4ce66efba074e863ca3e75609ad03d8e5bfb0bc81fdbeca37cc04ea84851f72d:eyJzZWxlY3RlZExpbmVDb3VudCI6OTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkwODUwM2U5MjRmYWVlOGVmMGJkMWQxOWIzMzlkODMxYmI2ODJmOWRmNGMwYzAyMTJlY2M3NjFkYmVjODNlOGUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImU3ZWExYWIyOTQzMjZhM2FlN2MyOGVjMjU3MjJkMmIzMDhkYzg1YmI3ZjBhYmZjODZlN2Q4ZDZlNDEzNWEwZjUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImFmZTg4OWUzNGY5NzEzZTMzZmYwZDlmOGVjMjI0YTJhMjhiMTE0ZWNmYTI4NzdjZmNkOTI4MTE4YzllNTU3NzUifQ" + } + ] + }, + { + "id": "claim_36aeefb38ede4937bd55e1639df6d194", + "statement": "The theme commands are headless by contract: dispatched in main.rs before CliContext::bootstrap so validating a draft never opens either database, they resolve the themes directory through read-only config::config_dir_path (never config_dir()), write no user state (no config.toml, no activation, no files), and are the only CLI commands that run entirely without a database.", + "evidence": [ + { + "resource": "repo://src/cli/theme.rs#L1-L29", + "version": "repo-lines-v1:sha256:6798c5deebb05b2411691be1052e5a418a6cf5307523bdb5983b7072a406f87b:eyJzZWxlY3RlZExpbmVDb3VudCI6MjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjcxZTQzZjE4ZjVkYmY3OWNjMTVlNjQzZWFiY2E5Mjk1MGQwMDg2YjM5ZmE4NDE0MzBlZDU0Yzg4ZGFhYTE5MmQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAzZmM1M2JjYWU3MWVkOWU0MzIxYTU0YWRmY2E0YjA4ODQxOTkxODdiOGUxMmZlYTk3YTIyYmZhN2RjYTllYjMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjRmZDkwMjM2ZDM3ZWMwZDU5NTJmYWRiNzQ3N2IyMTcwZjhlZjBmYTcwMTNlYzQyYTY4OTA0ZmY0NGNhNTZkYmEifQ" + }, + { + "resource": "repo://src/cli/theme.rs#L75-L79", + "version": "repo-lines-v1:sha256:0e6e53d335fe989ca94bd8a8dc4c045a0803b56d5b4e908901a2323d376fe5e2:eyJzZWxlY3RlZExpbmVDb3VudCI6NSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMjExNzQ3MTdjNmFjNzE1MTg4MTZlOGMxNGM3ZDQ2MGE4NjM5NjJlMTgyOTBmOWEzMzU5ZGQ1ZWMwM2ZhODMzMiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZjNhZjlhOTQ3MTBkODgyNjM4NzcxNzkwY2JlNGU1NjAwMmIyNTk4NWRlN2Q3ZTJiYTEyNjJlMzFhODhjM2FhZSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMDgwYTEyMWRlODFiMDcyMjNhNGQzNDlkNWJlNzlkOGY4NDU2Y2Q0MGFiMWY1MTE1NjU2YWZjMjYyYjlhYzNlZiJ9" + }, + { + "resource": "repo://src/main.rs#L37-L43", + "version": "repo-lines-v1:sha256:928fd295a26a9e0a2d143b2e0b7c628827081e48277d1c7e2fb6ca15322c9b04:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYTlhYTNjYTQ0N2E5Y2VmOTMwN2QxNjg0NDIwZWYzZTEyZTZhNmFlMDg0N2M1OWE0Y2VlNTNiZThjZGI5ZTcyYyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMzY4MGQ4MjI2YWZmZjZlMWQ2OTQzNTg4YzVkOWRiMmVlMjFkOWQ3ODZjMjNlYjIxYTYzNjIxZDI2ZjRkYzBhZSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMDVlM2NhMzQxNTE2MTM1YjNiMGJkMTgyMmE4MDhiOWNkMmUwOGQ1MTE5Zjk1ZWZkOTllY2M3NTNiNDgzYjBlMiJ9" + }, + { + "resource": "repo://tests/smoke/cli_commands.rs#L368-L463", + "version": "repo-lines-v1:sha256:ec47be4b9ea1bb5b9ab0ba92286ac46da77395ec8816ca07cf681ddf82dbd5d4:eyJzZWxlY3RlZExpbmVDb3VudCI6OTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQyY2Q0ZGI0OGEzNTBlOGYwYjQ3OTQ0YzQ2ZTNmZmRiYmExMzJkMjI5MGE5MWQ3MDEyYjU2ZWI0NmM4YWI0ZjYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImU1MjU3OWZhNTFhODM3YjNlOGJhY2I2ZmNlNzQzMjk0YTgxZDEwMmRhMDM4ZTQ2ZDRmMmVmMGIzYWQyYTI2YjkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijc1ZmZhZGE4N2VjOTA5ZjRkZGE1N2IwNDJjOGUyNjBiNTkzZjVmMWY3ZWQxOTEyNjEwOTQ1YmQwMjNlMWNjOGIifQ" + } + ] + }, + { + "id": "claim_8758e566a588429cbab8bb8235ad6b8e", + "statement": "theme check validates a single file in Strict mode with the app's own parser/resolver: the file's directory stands in for the themes directory so a package with a sibling parent validates as a package (a sibling-sourced parent is a warning, not a failure); diagnostics render as file:line:column severity: message with help lines including Levenshtein-based did-you-mean suggestions (length-proportional near-miss budget, lexicographic tie-breaks); exit 0 valid possibly with warnings, 1 validation/file error, 2 wrong usage; plain output escapes C0/DEL control characters from user-authored text.", + "evidence": [ + { + "resource": "repo://src/cli/help.rs#L247-L263", + "version": "repo-lines-v1:sha256:6fe9066405f3b55563882637c14e6149808aa13386cf3d5879106959b8331aa5:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVjZmRkZWY2MDY3ZGFjM2Y0ODAwMDRiZDA5Nzk4NDg1YmFhOWUxNzkwYTA0ODU0MGNhNDQzMzZlOGFiMzRkNGQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk0MzNkMGVhOWVjZjJmMzljOWE1MDQwNzM5NWVlNDExNzJmZTY4MmM5Yzg4NWEzNTA4Zjc1YmRlZTE1MWVmZmYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjA4NmRiZjdmNThhMmU4ODYzODQ3NWYzMmI4NWU3NmYyZGZhMWEwZmQzMDMyNjU5OWQ4ZTRiZDZiYjM5MTFlNjciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImY2ZmNkZDg0YzUxZmJhMGQ2NDZhMThjNmUwNzYzNmRmMmEwZWZkYmNjMmUyYWI2NzY3M2U3NTMxNmZlNGE5MjAifQ" + }, + { + "resource": "repo://src/cli/theme.rs#L546-L614", + "version": "repo-lines-v1:sha256:681c16723e87b5d1f1d876a8c979b8e01effb594578672fb2ad970764862c668:eyJzZWxlY3RlZExpbmVDb3VudCI6NjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ5MjAzOWNiZmIyZTEwYzQ3NDU5MDg2NWIzNDQ4MzE4ZjI0M2FjMDAxOTI5OGI5ZjZlYjk0MDJjYzcwZmI1MDgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQ4ODU5MjEwZWJjYjEyY2Y2OGM2MTYyMjJmYzU2MGMxZjY4ZTJmYTMxZjYzMDQ5M2RmODA5NTg4ODdkNTdhZjkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhhNTYzOWQ5YjE1ZmMxMGRmZGMyMmYzODJmOWU1OTJhMDc4ZDZhYjE1YjFlNzUzOTA4YzhjMDQ0MzAwMzViODcifQ" + }, + { + "resource": "repo://src/cli/theme.rs#L85-L155", + "version": "repo-lines-v1:sha256:6007236403ebd9374217c3a0e095519a27f7f19aa10223d6200c90763efd3345:eyJzZWxlY3RlZExpbmVDb3VudCI6NzEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk5ODE0ZjNmMDIzZTc2M2Y1YzdiNTBhYjZmMDQzMzhmOGViYTU2OWJkNzI0YWYxOGUwZTRhMjkwM2Q5NjQ4M2MiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjRmYjhjM2UzZDc4OTk2M2E1NGNkNzQ1NDEyNWY2NTRkYjY3ZGFiZDA5YzczMWFkNmM1ZDI2NDNjMGQ1MTI1YjMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjNiYzlhNWQ0YmI3MTBhZTY1NGZiYTZmY2RiYzg5NThmNjhiODBjYmE2NDQ0ZDM3NTYyNDRlZGY0MGMyOTI0NWYifQ" + }, + { + "resource": "repo://src/theme/validate.rs#L136-L193", + "version": "repo-lines-v1:sha256:38a1823e7e1174b8440a96d0c785ad93552472768bddd64570c7582b0005558e:eyJzZWxlY3RlZExpbmVDb3VudCI6NTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ0MDc1MDk3MmU4YjMxN2E0OTAxOThhMzk4YTBmZDU4Yjc1MTM0NjBhYzFkMjI3ZTU1MDcxZGMxM2RkOWU3NWEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYWUxZWEzODk3MTJlYjcyNmZjZjg4MjllODJhMzk0YjFhNmE3NDVjMmFjMzkyNjYwMWI5N2E0MTJjM2FmNzciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImFkNWFkNWMxMTQ4OTEwNGM3ZWYzMGQwNmI2YmJmYTBhOTliM2M5NjI3M2FjMTY0ZjEwZTA4OTE0MTg2MjBmZDAifQ" + }, + { + "resource": "repo://src/theme/validate.rs#L75-L130", + "version": "repo-lines-v1:sha256:7bf4850a90091a07e2a309e3d269b81d8a8792604f4738c85e6859aece6e5ece:eyJzZWxlY3RlZExpbmVDb3VudCI6NTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijc0MTEzMWExYzNlNGIwM2NkYTVhMjE2ZmNhOTc2MGQ3MDJiZTZjODRlNjA2NGYxYzFkYzAyMmEzZTg3ODQ5NzEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjdhNDBjYjk1Y2ZmNDU4ZTc1OGJmOWYzZjViMWU2MzRjNTYyMzdjNGE0ODQxOTJmMjc4N2VkZDkxMjVlMmRjMjMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImY3NGEwMjkxYzljYzhmNDJiYTRkMmM0YjU1YjIxN2YwYTYyOTI2MGQwNTExZjI0OTcyNjY2NDA1YjEzNDUyNjgifQ" + }, + { + "resource": "repo://tests/smoke/cli_commands.rs#L618-L653", + "version": "repo-lines-v1:sha256:6a95547850d80082cddf62c883cfc02f23811f8a6b7c46dd9477be79b966d797:eyJzZWxlY3RlZExpbmVDb3VudCI6MzYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjUzYTM0MGJkNDVjNGJhMzVjYWQzODNhNzMwMDRiYjU3NDc4YjJjNjIxYjlkM2NhYzVjMDlhYTA5NjRiZWJiOWUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjdmOWFkNDYzYmU3MWM1ZjQyMDk0NmFmYmYwN2QwZmU5ZjVmMTFkYTgyZjFlOTQ5MDNjYzMzOTVkOGMxYTBiMzgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImIwZGI2YTA0ZTIyMDQxNDNjODdlM2Q2Y2FhZTBmMWEyM2NmOGU2N2Q5MzRmNDljOTZhMGQ0MWUzODUwNTMzNzAifQ" + } + ] + }, + { + "id": "claim_e87316493e3a4187b126c6914764f324", + "statement": "theme list builds the registry in Compatible mode — exactly what the running app would see — and reports every record including invalid themes and user files colliding with a reserved built-in id (never filtered by get()), plus directory-level diagnostics; a readable themes directory always exits 0 (theme state is the output) and an unreadable one exits 1.", + "evidence": [ + { + "resource": "repo://src/cli/help.rs#L264-L273", + "version": "repo-lines-v1:sha256:66dd6895319d99e579694747ff012cb88af02bc8f277eeda5cf61c937fde9912:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjhiZTRlNDliNDc1NTNjZTY0ZjNhYTE4ZTcwMmYzNjg0N2Y2NzdiOWQwMjgyMTM2MzhmNGI0N2NlOTAzZDQ4NzIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk0MzNkMGVhOWVjZjJmMzljOWE1MDQwNzM5NWVlNDExNzJmZTY4MmM5Yzg4NWEzNTA4Zjc1YmRlZTE1MWVmZmYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijc3YjIwMzkzYWYxZjJjMWQ1MDI0Yzg3MGY0MTRjNDViYjliNmY4NGJhMmQ3ODdiMTJlYWM1ODc2OWM3MjIxYzgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijc3MDJiZmY2YTE5MGJhMzYzYzA4MjBkYjUyMzBmZTZkZjBlNGViNTA3ZGNlMTYwMjNjNmJiNWRkYTYxNDAxNDAifQ" + }, + { + "resource": "repo://src/cli/theme.rs#L236-L342", + "version": "repo-lines-v1:sha256:444f5aec68d9f293602f261f2064d62c761e596ef9cca097e48609539e3310c5:eyJzZWxlY3RlZExpbmVDb3VudCI6MTA3LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiJkMDY0ZjMwNWQ4YzY0MTJhMDY1YjgxZDdhNjM4OTEwNTJmMTUwZTRiZWMyMmRlYzc0MmY3Yjc5Y2IyZTRmMjU2IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0MTJjYTM0NWNjZjc1YmY5YzA4MDZiY2U2OTViZThkZTgwOGI3OTk4NDI1MWE3YTU0ZDIwMmNmNjEwMWRkNDUxIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiIwOWYwNmQ0ZDhjNWU4NWZlMzQ2OGZlOTliNTQ0Mzg2MWI0MzQ4ZTdjMTNkZjg0YTBiNjM4NTRmMzYzNWNlNTgwIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiJmYzI2NjUwNTJiNTEwODE2YmUwYjc5YTk4MWVlZTA5NDgwYzljNDg5YWMyZTY2MGYyMTkzNjU0ZDAwN2VkMWRmIn0" + }, + { + "resource": "repo://src/cli/theme.rs#L260-L273", + "version": "repo-lines-v1:sha256:bd3cec212f62ac5aff3be5e5c7ca5a92d0e1ec5f92883338f21e6299337fa46e:eyJzZWxlY3RlZExpbmVDb3VudCI6MTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM2Yzc4ZDYwMzJlZTg3OTMxMmYyYjJmNzgxOGY2YWI5NzFiZDFkZWZkMTQ5ZWYwYWIyMTAyZjc1ZmY1YmI3ZjkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImIwNDY1ZmNkODE3YzM4OGRlZjI2YjRlMjEyMGQzNGE5ZWNjYjI5ZjhkY2RmOGRkMmIwODUyMGJjYmU4NmU4MGQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjIwZWU0Y2NjZWIxZmE3NDE2MjA1MzhmM2RiZjUxYzgzMzk4ODUxMzgxZjI4YTBlYjYwYjhkNTdkMjA1OTlkMmIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQzMTEyZWE1Yzc2ZGRmNDhmYWI3MmE5M2FkNWNmNzgxODJlNzQ2MmY0ZDg5OGE1YzExYjNhNGQzN2Q3NjAzM2MifQ" + }, + { + "resource": "repo://tests/smoke/cli_commands.rs#L655-L705", + "version": "repo-lines-v1:sha256:373acb558541e89597a89a0cf3ef34d2c9543a3677d05492db9bcde53364fc36:eyJzZWxlY3RlZExpbmVDb3VudCI6NTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQyY2Q0ZGI0OGEzNTBlOGYwYjQ3OTQ0YzQ2ZTNmZmRiYmExMzJkMjI5MGE5MWQ3MDEyYjU2ZWI0NmM4YWI0ZjYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImM0ZTAyNTFjOWVhM2FmOTg5Mjk2YWQwNDRiMDBlYjYwMGE4OWUzZDRiMDllYWEwMWQ1MmJmZTY2YzhhNTM5NTAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjgzZDk0NWZiMDM3NmIwZGRlM2ZjM2ZjZDQ3ZDE1ZDdjMDU1Y2U3YWZiNThjY2M1ZjMzNDk0MDI2MTExMGQ2ZjYifQ" + } + ] + }, + { + "id": "claim_f3f6e0a3cdeb4b7b81cbc2248dc78f7e", + "statement": "theme show uses its own toml|json format set (no plain, because show writes a document): without --resolved the file text is printed verbatim with comments for the copy workflow (unknown or invalid id exits 1); --resolved writes a standalone document with no extends and no references — every semantic slot, named gradient (synthetic names for nameless ones) and component role emitted with toml_edit escaping and quoted keys — which re-parses to a semantically equal theme (round-trip tested).", + "evidence": [ + { + "resource": "repo://src/cli/theme.rs#L1054-L1078", + "version": "repo-lines-v1:sha256:fa3ccb952c47f79b9ae9a2e97b13bdfff0d8aad0fb915ef23648d62ec83f8ea6:eyJzZWxlY3RlZExpbmVDb3VudCI6MjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkyYjVlZGY1OTQ5Y2ZlYTY3ODIyY2Y2OTExMzIzNjNkNjhkNmYxZTE2OWY1ZTBkNGY3YzE0ZTVmYTFlNmQ0ZTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImI2NzQ2ZGQxNDY1NjZhYTJhYWI0ZDA4OTBmMmNkODhkZjMyYWJhMWY5M2M2NmQwYjdiYzU4NjMwMGRkOWI0YTQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk0YjMwZDNkNWZmMzZmNzgzZmJjMzBmMjRmNmY1MjA5MDY0OWJmMTZiYjY3YjRlNTMzY2Q2NzFhMDEzNjUxZTQifQ" + }, + { + "resource": "repo://src/cli/theme.rs#L348-L465", + "version": "repo-lines-v1:sha256:d4bb85f37a36b1c934f231efc7fbb20b86ca7a56fcab30561f81f241cda2e29b:eyJzZWxlY3RlZExpbmVDb3VudCI6MTE4LCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiIwMjA2NDU2ZjBlYjlhYzYxYTY5YWY5Y2I5YWMyZWU0ODZmZjExOGFlNmM3MTQzZDU0MThjMjVhNTUyZmI4YzI4IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0MTJjYTM0NWNjZjc1YmY5YzA4MDZiY2U2OTViZThkZTgwOGI3OTk4NDI1MWE3YTU0ZDIwMmNmNjEwMWRkNDUxIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiIyYTBiNjg3MTdhMGNmOTM5NmRhYjVkOGRiYTQ2ZjAzMzRkYWU1NjdkZWNiZjQ0Mjg1OTA5NzhhZGIwMDNkYTAwIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiJiMTNiNzY5MWM5ZDcxZDNlZjZiMjE5MjQ1NmQ5ZmFkYTdkYjU5OTgzMzc5OTRkMDQ3NmI2YzMxMzkxODY4MDJhIn0" + }, + { + "resource": "repo://src/cli/theme.rs#L898-L919", + "version": "repo-lines-v1:sha256:63563b60ad9c2b3873dc3875e6546f4a2d7d395f5f32f51545d82e961795b11f:eyJzZWxlY3RlZExpbmVDb3VudCI6MjIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjYzMGJjMzM0OWY1MzQ2ZmE2NTI2MDNjMTBlODVlZDE4YzYxYTdkMmIyMDlhOTgwOTMyMGQ2OThjMmQ1YzE2NjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRmYTk2ZTBkMjgyMmViNjNkYjZiOTc4MGIyYWFkMTNhOGVhMjI3MWVhMWQxYzI3MTkwZTYyNTMyNjk2NTAwYTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjRmNDZjOTgwM2FkZmQzYTY1ZTBlM2QzZDNlYmNlOTFiMGQxODIyM2IxM2Q4MTU2MTZmMTYzOTVjMDIxNmMxYmMifQ" + }, + { + "resource": "repo://src/cli/theme.rs#L929-L1038", + "version": "repo-lines-v1:sha256:d5da820e61b6f5b3d8bf4cbc44402e7ca8ce01002589bb01fb3ef30d78e0aacc:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEwLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiJhNzBkMjgxYWY4MzkyNDQ3YWQ3Yjg4NjJhMzJiNWExYmZmNmFlZDdjZTdlYzI0ZTA1ZjY3NDM2ODA3MjdiOWQzIiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0MTJjYTM0NWNjZjc1YmY5YzA4MDZiY2U2OTViZThkZTgwOGI3OTk4NDI1MWE3YTU0ZDIwMmNmNjEwMWRkNDUxIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiIxOWU3NjM1NWNmMTE5OTQ4MGMzZjk5ODM2YjE0ODZjODQxZDg5MWZhYWQ5MDRjNjA4ZTA5MzAyMmQzNzU3YWVlIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiI3YmM5OGQ2YjBkYzQ5MmM1MDE2ZGYxMDgwNWEzYTU0ZWI5YzE4NGE1YTYxMzdlN2Y0YmVjZmEwYzIyMDU1MGQwIn0" + }, + { + "resource": "repo://tests/smoke/cli_commands.rs#L513-L586", + "version": "repo-lines-v1:sha256:7e388bd3c5398a76eb13b850cd232fa6071d0790748aa872c0342d76a7cfaa1e:eyJzZWxlY3RlZExpbmVDb3VudCI6NzQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQyY2Q0ZGI0OGEzNTBlOGYwYjQ3OTQ0YzQ2ZTNmZmRiYmExMzJkMjI5MGE5MWQ3MDEyYjU2ZWI0NmM4YWI0ZjYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQyY2Q0ZGI0OGEzNTBlOGYwYjQ3OTQ0YzQ2ZTNmZmRiYmExMzJkMjI5MGE5MWQ3MDEyYjU2ZWI0NmM4YWI0ZjYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjBlOGE2YTM5MDQ3MTljMTZhOWM4Y2FkZmRlNDRkMTBiNzBmYzc1NzMxNzgwMjMwYWJjYmQyMDc4NzIxYWY1ZDUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijk5ZWY0ZjQzOTNmMTA1MzIyNTFkM2NjMzJiMmQyZmE3ZmNhZWJmMmVkOTljOWIxY2YzYTE2OGZkMjU5MWI5NWEifQ" + } + ] + }, + { + "id": "claim_9fe8ad657a684560af229cfc5f5fc0a5", + "statement": "import --from selects ssh (default: ~/.ssh/config, PATH ignored, --dry-run explicitly refused with exit 1), termius (export directory containing L00t.csv, auto-detected default), putty (a .reg file or sessions dir defaulting to ~/.putty/sessions), or mremoteng (confCons.xml, PATH required); --dry-run parses and previews name/user@host:port lines with a count without writing for the other three sources; only SSH sessions are imported, encrypted mRemoteNG passwords are not decrypted, and an unknown --from exits 2.", + "evidence": [ + { + "resource": "repo://man/sshub.1#L290-L327", + "version": "repo-lines-v1:sha256:1e4cf7e23d3be38af7bc5cdcfc44943033b22c2f03a5c65074e7d9942dbdc0d7:eyJzZWxlY3RlZExpbmVDb3VudCI6MzgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImY1MmNjM2ViNDc5MTg5MGFhYmZkNGIzMzU0MWE2MjA5NGE5ZjcyM2NhNjNkZWQwMWFkYzdmMWNkOWQzOGY5NzEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk5ODg3NzNjNzViYjkyYjc2YzhkZjhkOGY3YTIwMTI0MDk4NWNlY2FjYTk2MmIyMTMwNjVkNDVhMmQxOTUzZDAiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQ5YWRjMmIzZDYwYTgwZmI0OGNlOGNjNjI4Yjk2MWZiMzhlZjMyZWExN2JhZTU5NmM1MmM5YWMzM2MxN2YwNTAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQ1ODg4OTNjZjU5Y2Y2MjNhYTQyNGE0OTBlY2U5NWQyOTM3MzhhMDZiOTljZGIxNzFjYmFkNzM3Yjk5YjBjNDYifQ" + }, + { + "resource": "repo://src/cli/help.rs#L214-L230", + "version": "repo-lines-v1:sha256:d9439e72440b73fa8688c82915597600e86ae581933584637b66211b2c713118:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA2OTA0MjJhOTU4NDgwNWYwOTEwMmZiYTE4YjMyZmM4YzE3ZTczNTI1ZDUyMjYxNWE1MDA5ZDNhMjZmYzUzZDUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE1OTM3Njc5MWU2OTQ3NmY3YTA3MTg5NmRjMjViOTEzN2VmODQyM2UzZTk2MzQwY2NmYWMzMzIzZDQyY2E1MWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE5NTAwZTU3YmQ3MWM5NjQzMDExYjNjYmRkYTVhZDRlMmNmY2UwZjc2YTljYzQ2YTkzYmRhYTczNzBlMzE1NmIifQ" + }, + { + "resource": "repo://src/cli/inventory.rs#L47-L168", + "version": "repo-lines-v1:sha256:7f11fb039ca0cec78aef30a45252a32574631b516b43fc37aa3e25231dfe48d6:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIyLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiIzNGNhZmMzNTY3MzVlNmI5NGI4NWM0OGI3NzQ4YjYyODk2MmIyZWI1NTc0YjFlNjYzOGViOWI1ZGUzYzAwOTQ5IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0MTJjYTM0NWNjZjc1YmY5YzA4MDZiY2U2OTViZThkZTgwOGI3OTk4NDI1MWE3YTU0ZDIwMmNmNjEwMWRkNDUxIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiIzY2RiYzAzYmMxM2U4MDIyZDMzMDc2ZjA4MmYwZTIwZmE0N2JhMWNlNjkxNmM4YTZhZjQ0ZDMxNWNkZDQ3MzI4IiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiIwOTBlNWQ5NjE0NTZkMzFlNjZiMmJiNTU4ZTA2OTA2MGM5OGViZThlOGE2MTE4OTkzYWFmMmE1Y2UxMTg5YmU4In0" + }, + { + "resource": "repo://tests/smoke/cli_commands.rs#L247-L315", + "version": "repo-lines-v1:sha256:974695fd2bfc7ae5df2828cd94b9830826c71f2df41fc78f16996448a604adc3:eyJzZWxlY3RlZExpbmVDb3VudCI6NjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQyY2Q0ZGI0OGEzNTBlOGYwYjQ3OTQ0YzQ2ZTNmZmRiYmExMzJkMjI5MGE5MWQ3MDEyYjU2ZWI0NmM4YWI0ZjYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNiYTFmZmQxNmFmYWVmNzQzNjkwNjVkMjFlMjA4ODZlYTQ4NzllMTM5YjU0NGViODViN2EyNjU0YzkzYTViOGMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImNmOGFjMjM4ZWQxN2FlZTYxMzI3ZWIyNzZiNmQ3MDMwMWYwYzdhMDE4OGE2YjRkYWZmZWRlNTQxYTIxMDlkY2UifQ" + } + ] + }, + { + "id": "claim_6046e8e402c64ec588ebfed4f0449500", + "statement": "A name, address or username starting with - is refused at the write (create_host and update_host reject_option_like, issue #101) because ssh would parse it as a flag — an address of -oProxyCommand=id runs code locally; importers surface the same refusal per row (one poisoned entry does not cost the file: the summary counts skipped_invalid as refused and stderr names the host), and a legacy stored target is additionally neutralised into the ssh:// URI form at connect time, which OpenSSH refuses outright.", + "evidence": [ + { + "resource": "repo://src/cli/inventory.rs#L218-L244", + "version": "repo-lines-v1:sha256:d00c845bb1329f671254f989272871fadce83f0b4ce281174aa483dfaa501112:eyJzZWxlY3RlZExpbmVDb3VudCI6MjcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjZhOTRkODViZDA1OWRiOTRhZTA1MWI1YThmYThiYTgzMWMxZmU4YTYxZWQ0MWM3MGViMzZlNjMwZDA1NTEyYzQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE1OTM3Njc5MWU2OTQ3NmY3YTA3MTg5NmRjMjViOTEzN2VmODQyM2UzZTk2MzQwY2NmYWMzMzIzZDQyY2E1MWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjMyY2U4YjI0ZTJkYWJhZDc3NzUyYTZjY2M2Mjg1ZGNlZWI2NTBjZDEwMjVjNTI2MWMyYzkwNGViM2JlNzQ2YTIifQ" + }, + { + "resource": "repo://src/ssh/host.rs#L88-L107", + "version": "repo-lines-v1:sha256:0855157bae5856cb010151d37ec33abc461233c80742b5d9cd9c0057cf0028b9:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRhZDMwNDVlODI0NDA2MmYyMzYwYTU3MzdmMTZmNDkxYmNmNDJlMjJkOTg5MWIyYTk1YmRhNTJlNTUxZWVkMGMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk3M2ZhMTMzYzY3NTU0MzE2NzIyZGI3YWNmNDI2NDAzNGNmZGRhMGQ5ZWNkZThhNWM0MjY3NjVkOTQxODA5YzciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImM1ODc1ZDMzNmI5NmVlMGNkZjdlNmJmYjg4YmU0NDRhYWQzY2MwMGZkZjg1Yjg3MmZiYjRkNTA2NTAyZTMyZWEifQ" + }, + { + "resource": "repo://src/store/hosts.rs#L204-L209", + "version": "repo-lines-v1:sha256:6ecb6543212b8103e776a20aa5aab2db644e93dfb1051ab712c13ad27b61a065:eyJzZWxlY3RlZExpbmVDb3VudCI6NiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYmYyNTU3MTUzZGFmYWVlNThmYzMxNTgwZTViODdjZmZmMzgxZjY0MTliM2E0NmMwMGZjMjA5YmE3MWExZmE0NiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZTZiMDQ0OTE0Njk3MjJlODM2M2E5NGU0MDQ0NTM4ZTAxM2EyYTIxN2UzNmIxZTU2OGJhNjBhY2I3YjRjOGYyZSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMWYyNDA4YTU3YWJkMTJhY2VjZDA0OGRjNzBlNWI2YjYwZWYxMjRjNjcxOGIzMzhmNzk2M2E0MjI4OWYxMWJjNCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNjEyMmFhZGQ3NGZkZDgxNWIyMjEyZWQ5OGZhMTMzMjNlY2YyN2EyYWFjM2QyOTQwN2RmNDZhNDZjZjEzNWRkNyJ9" + }, + { + "resource": "repo://src/store/hosts.rs#L311-L320", + "version": "repo-lines-v1:sha256:c362f51dcb9e77a48f77e694df2579e2ced45ca569f4653185f1aaf4b3264da6:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk2NTI2MjBjYjcyZWFmM2IyY2E4NzcwMTRmYTdmZjNjOTQ3ZTAwYmNjYjVlNzY1ODg3MTZmN2JhMTgzMjZkOTQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImViOWEwOGJjMjlmNmViZWJlOWE1ZjAxOTAzZDk4NDVkYmNiNTg0MmE0NTVmMDdkNWY0MDAyZGFjMzJhNWM2MDUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhlZTRkNDYzMGU5MTBmNTE3Yjg1NjQ0MmYxYmEwZWQ1NTZjMDM3MDA3NmRmNWU3MGU1OTIwMGUyYzA0YTg3NjAifQ" + }, + { + "resource": "repo://src/store/hosts.rs#L894-L910", + "version": "repo-lines-v1:sha256:1e7deb4788bbbdf735e1e05ed3b837e602ed9b94c8fb2a0bd58517788ae1afd0:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjJmMDZmZTRjNGM2NzFiMTAzZWUyZjhiY2E0NjcyODdlOGI2MzhmNzU1NzE5YmVkZDdmNmNiZDhlMTIzMDEwYWYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImMxMzYwMmYzNzVkN2U5M2RkZDEzYjAzYTQ3ODI4Y2UyZDkxN2MyNThmOTdkY2M2Mzg5MWFlN2E1YmIwNWM5N2IiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImEwYTEyNjk5YzE4MDk4MDg0Mjk3OTNhOTNkZGU2MDcwYTU4Y2VlMzBmZmIxMmQ4MTEyY2FjNjI0MmU0YTI3NjEifQ" + }, + { + "resource": "repo://tests/smoke/cli_commands.rs#L926-L975", + "version": "repo-lines-v1:sha256:1e7409101a5cce0a46994b3ad266b51db634859b684a744f57c5fcbdfc920ce3:eyJzZWxlY3RlZExpbmVDb3VudCI6NTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVjY2NjYmIzM2E0ZmRmZDAyNmQ2MzQzZDI1YWRkZTNjMjUwMjVlYTBjNjU4MTNlZTYzYmY2ZGEwYWYzNDJkOWMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjIzNzEzODdhODA3MDlhNDk2ODcwNjE5NTVjZjczMmE1ZmU0MzJhNmQwODhmYmUzNzQwMzZmZDlmOGM3ZTQyNzEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ" + } + ] + }, + { + "id": "claim_5d3249760b854513ae1d67463404a94f", + "statement": "audit list supports --status all|ok|fail|retry, --via all|connect|tunnel|agent|exec, --host, --days and --limit (default 50), where connect means interactive connects only (the store filter is via NOT IN ('tunnel','agent','exec')); audit stats counts ok/fail over a --days window (default 30) and only surfaces retry with --include-retry; both validate their enum values and exit 2 on a bad one.", + "evidence": [ + { + "resource": "repo://src/cli/audit.rs#L25-L119", + "version": "repo-lines-v1:sha256:5ada56536a228e6d9ec2f18e4526586ffa367082905a9da44eea4961452cdb3c:eyJzZWxlY3RlZExpbmVDb3VudCI6OTUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImM4MmI4YTU0NjY4NDFjMDhhNDdmNjk1ZTBiNWY0N2ZkZGUxZmNjY2ZlYTMwOTE1ZGZiMzdlYjQwZjA3OWJmNTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjE4OThmYjZkYWQyNzY1M2NmZjU4MDlkNWFkNjA3YTdhOGMxODFiZjNhZjU1YjkyNTk5NDk5M2MyMzNiZjY5NzgifQ" + }, + { + "resource": "repo://src/cli/help.rs#L184-L193", + "version": "repo-lines-v1:sha256:c5a74b4f38f8360d25ce148773a1aab191f2d6c1837abbc28910eaf4942db480:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjUwMWJlNTZiZWFiZTc4NWNiM2FhMjJiNmFkNTcxYmQ4ZTkxY2U0ZGRhYjQ3OWZlYTcwNWM5NjI5MDQ4MTNiZTQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijg0ZTQyMDhjZDZmZmI4YzRhMzU0N2IyZjU0YWRhMTk5NzhjM2YzNjVlNzhmOTUwMmVmNTE0NWNkZDJhYTg1NDMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE1OTM3Njc5MWU2OTQ3NmY3YTA3MTg5NmRjMjViOTEzN2VmODQyM2UzZTk2MzQwY2NmYWMzMzIzZDQyY2E1MWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjNhMWZmZWJhMTRlZjQyMjc1ZDI5ODhkNmJjNzMwODJkNjI4OGIxMTU0M2QxMzU4N2ZmYTEyZTkzOWQzYjE2MjcifQ" + }, + { + "resource": "repo://src/store/hosts.rs#L878-L921", + "version": "repo-lines-v1:sha256:33972cc34ff636bb15ee17153969b6ff58ddb0d82bf37840b3f1f5ee345c6631:eyJzZWxlY3RlZExpbmVDb3VudCI6NDQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjMzNDFjMzAxZTU2YThmN2ExODQ5ODU4NmFlMjdhZWYzNTIzMDMyMmM2NWY2NGFjZTE1ZTBiNTZiYmIxZGJkZmQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImVjZTk4M2Y4ZjQ0N2RhMjU2OTBlZTg2OGEwYzM5NTczMTUzZmNmNGE5ODU3MWY3Y2MzYjRjMDk2OGNiMzY3YjMifQ" + } + ] + }, + { + "id": "claim_6d3b362ea5954dec9ae94b0a5bfb7b6e", + "statement": "tunnel start resolves its argument by numeric id, label or local port (ambiguous matches are an error) and detaches by default (spawn_detached_tunnel with PID files under the profile tunnel dir, mutually invisible to the TUI manager) while --foreground runs in-process with the reconnect loop and exits nonzero on gave-up or error; tunnel delete requires --yes and stops a detached instance first.", + "evidence": [ + { + "resource": "repo://src/cli/context.rs#L114-L137", + "version": "repo-lines-v1:sha256:b5d05612c1ded73e6a995957ad7c00894fcae7285d269fea6ce819e9c01c2803:eyJzZWxlY3RlZExpbmVDb3VudCI6MjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImMwMGJiZjc5ZDZmYzExODFkYzBlMTMyYjE1ZGZmODNkNmZhMjA0YjhjYjQwOWZhNWJjMDlmMDRmYmZhZjc0YjIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5N2Y4NTgwNjA3YTJjYTdkODc4YWZjZDc4ZGY0MmY0NTAxOTU4NjEyZDQ5Y2ExNDFkOTcwZTg0YWEyZDA2ODkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImZiZGMwYmI3ZDcxNjExYTNjMzRhYWQyMTU3MmVlMDBjYjFmODg5MDU3ZTk4MTRmOGZkOTExOTllYWUzMjgyZGYifQ" + }, + { + "resource": "repo://src/cli/help.rs#L148-L163", + "version": "repo-lines-v1:sha256:b441d6338016593dd949d83496bc412c3f64e863f022e73c23e58d19876b7d8c:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE2YjFmNzRjMzRlZGFlMGE4ZWZhODdlN2EzMWRiMTZkOTkyNDQyM2Y3NzM2MzU5MDA1ZDhkMTBkNDc5ZTM1YjkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijg0ZTQyMDhjZDZmZmI4YzRhMzU0N2IyZjU0YWRhMTk5NzhjM2YzNjVlNzhmOTUwMmVmNTE0NWNkZDJhYTg1NDMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE1OTM3Njc5MWU2OTQ3NmY3YTA3MTg5NmRjMjViOTEzN2VmODQyM2UzZTk2MzQwY2NmYWMzMzIzZDQyY2E1MWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImY1YjEyNDcwNWVhMDI4ZjljZTQzZjc5NmUwMTI2NzY1ZDc4ZmQ5YzY1MDE0MTA1MDRiOGNiMzZlNzNlNjRjYjUifQ" + }, + { + "resource": "repo://src/cli/tunnel.rs#L166-L209", + "version": "repo-lines-v1:sha256:d3603e2ac1f59f5c612f11ff16e83407ac75b96432d82edabac09ce23342c8df:eyJzZWxlY3RlZExpbmVDb3VudCI6NDQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijc3MTAzMzA2MzM1Y2I0NTBhOTZiNDg4NmUyOTc3Njg4MDZhZTVkZjc4Mjk4MmU2YzhkZWVjYWQ5OGUyMTJmM2UiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjM1ZjZmNDY2ZTU5Y2ZjMzA3MDNjNDVmNzJhZjY1ZGU1NGFjMzYzZDM1NGNjMWEzMzMwMzUwYWNkNDUyNDM0YzQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjRlMTBiOTliOTUzMDJiZmQ2MTgzMDg0MjYwN2UyZmMyNmQ4ZDJhZmJkYzlkNzViMWNlM2JmZjk3OTAxOGNhODUifQ" + } + ] + }, + { + "id": "claim_52d2ca61492d4ebeb11904453acbd65e", + "statement": "Each sftp subcommand drives the background SFTP worker synchronously (send one command, block on the event channel), rejects ProxyJump hosts up front because the libssh2 transport cannot chain a jump, and requires --yes for the destructive recursive-capable rm before connecting; ls supports --format plain|json.", + "evidence": [ + { + "resource": "repo://src/cli/sftp.rs#L21-L78", + "version": "repo-lines-v1:sha256:43f987869cdfedf0cf5b7a4708272e9eb7be9573586c4318f0453ebe37bcb892:eyJzZWxlY3RlZExpbmVDb3VudCI6NTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImUzY2Y2MDA3Yzg0ZDBhYjg3ZGRjZGQ2NDk1YzcyNGU5YjUyNTA5Nzg4MmNiMTg2NmQ3YTkzZGJkZTQ2YThkNWYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImVlOTI0ZWJhNWQ1OWFkYTBiNDg1MjA0NmI4ZGFhZmYwY2NhNTM5Mjk0ZDJmMTI5ZThmYTJmN2I4NTQ5ZDQ2NTciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQ1ZDA3Njc3NTVlZjIwZTU5YjZjYzA5ZGYzZTY2MmRlNTkyMTE0Njg1NjkyYTg4OWNhODliNWUzNmE5ZWJmNmEifQ" + }, + { + "resource": "repo://src/cli/sftp.rs#L258-L289", + "version": "repo-lines-v1:sha256:aa4a5c2e3443d8b669c1e3787ef32958b43ba67ebf36a4d56e590158b63251c9:eyJzZWxlY3RlZExpbmVDb3VudCI6MzIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkwNDkyNWNiYTQ1OTgzMTBlYzkwYmJiNjBlZTNjZTgxOTNhMzkwZmQ0MjA3MjMzMjc5YTQ0MzgwMjllZjEzNDEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjY5YzQzZTcwYTVhZDk3YTMzM2U3NzQyMzMxMzE0MDU0MjkxZjlhYmZiOWYwMmZhYjBlMDc2MjI4NmMyZjFmNDUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjYwZDQ5MDg4NjQ2NmI2ODU4NGE2ZTg5NWY4ODc0YjBiNzgyMTViODQ2MTk1OTBmNmE3MzZiZWE0ODY0ZjRlYmUifQ" + } + ] + }, + { + "id": "claim_2b944146316c4a5fa16bddd1101e3869", + "statement": "completions bash|zsh|fish prints a static completion script for the top-level command tree; host names are fetched by re-invoking the same binary with host list --format json (or read from a cache file) and --cache PATH both reads an existing cache first and writes the freshly-fetched names back with a header comment and restricted permissions.", + "evidence": [ + { + "resource": "repo://src/cli/completions.rs#L14-L106", + "version": "repo-lines-v1:sha256:eddf8971b3b6a9af6cabb87af535fdcf394bdbf35d2555801c28d4887521dc94:eyJzZWxlY3RlZExpbmVDb3VudCI6OTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjcxMGRhNTcwMDkwMDEzNzYzMDQ3MmFmZjFiZTM0ZjlmNDI5ZjVjYTFhZjhiODEzMjgxOTYyNDE2NTNhOWMzZjUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk2NTMzYmQzZjg3ZjM3YmMxMjY1ZmFjOWM4MDJhNzBiNWY3MjQzN2NlY2YxMDUzYzdlMzA5MzFhNTRmYmNmY2UiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJjZmE2YWNkY2I3YjdmYjM1ZDUyOWU2ZTk4NzMwNjIwNzY0MzkyMjFlMmU2NzU0N2UwOTg2NGUyNmZiZDc0MmEifQ" + }, + { + "resource": "repo://src/cli/completions.rs#L148-L175", + "version": "repo-lines-v1:sha256:3a35c4805fb98facfb8cf78d6428d04e287be24bfe04ca1e79de4ca639e045c3:eyJzZWxlY3RlZExpbmVDb3VudCI6MjgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImIyMThhODBjZjBhN2I1NDNmNDAxMjFlY2YyNjQ5NGE1ZTQ4NTg4MjZhYzcyMWYzYWE1ZDRmMzZlYjczMWFkMmYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImZlYWQzZjI0OTZkYjg1YWQxMmI0M2QzYmZmZjRkMzcwNTM3MGI2ODlhYjAyM2Y3Zjk5OGY3MzZiYmRkOTk0MDkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjkwMzRmM2I4MTNmYTU3YTNlOTMwYmMxOWU5YmUwMDIzMTk2ZDRjODE5NmQyNzU5ZmQ2Y2IzOTU3ZjE3YTU2M2YifQ" + } + ] + }, + { + "id": "claim_1ff63ac3357f47a9a62161f5339ebb84", + "statement": "Host names are not restricted to a safe charset and the generated scripts are sourced by the shell, so each generator escapes them per shell dialect (bash double-quote escaping neutralizing $( ) and backticks, POSIX single-quoting for zsh, fish \\-escaping) to prevent completion-load-time command execution.", + "evidence": [ + { + "resource": "repo://src/cli/completions.rs#L181-L213", + "version": "repo-lines-v1:sha256:53b61f068fa1ec4b19a60e6146eb4ac2c5451140221b557198603f0893eb0efc:eyJzZWxlY3RlZExpbmVDb3VudCI6MzMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjE3ZWNmMjBiODZiMDNjYjA5NjIwMzA3NTc3ZjRiMTUwZDk5YTI1ZjI0MjJkODRkMmE2ZDUwYjUwMjUyYTI4ZTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRiOGNlYjRiNTI1NjIxMzU2ZDgyYzM3YjZmM2VkNGMzYjhkNTYyZDk2ZTRiZjhmNzA5NDQyOWUwZjE4ZWUyMTAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjUwMGE4ODgyZDU2ZGFkNWQ0YmUyMDMwZTVhMWUwNTIzYjAyYzg0YjNlYzE2YWJhNTgyNWRjMWNhNDcyOWE0YjYifQ" + }, + { + "resource": "repo://src/cli/completions.rs#L536-L554", + "version": "repo-lines-v1:sha256:89049600db62f031cb0287e991475367785a4c6c74c96ae22fb1925c7cc09f02:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkyYjVlZGY1OTQ5Y2ZlYTY3ODIyY2Y2OTExMzIzNjNkNjhkNmYxZTE2OWY1ZTBkNGY3YzE0ZTVmYTFlNmQ0ZTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEifQ" + } + ] + }, + { + "id": "claim_2a18c60db9b4488caa176eda95762927", + "statement": "sync refreshes the ssh_config-derived rows in the store (sync_ssh_config_hosts) and reloads the merged host list; export renders launcher hosts as an ssh_config snippet with --stdout and -o PATH mutually exclusive, defaulting to a file beside the profile's config.toml with restricted directory permissions.", + "evidence": [ + { + "resource": "repo://src/cli/inventory.rs#L170-L209", + "version": "repo-lines-v1:sha256:e5dbe2b71271d3f09774dd164fcbafeb609d4a88b9c1e45f0b1008a52e578c24:eyJzZWxlY3RlZExpbmVDb3VudCI6NDAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjIwNzQ1NTIxZDBkODNiNDRhMzc4OGM2N2EyN2ZmYWVkMjQ1ZjFmZjM4MWNjYmE5YWY2YjRjZDk1ZTM4OWI4YTIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNjZGJjMDNiYzEzZTgwMjJkMzMwNzZmMDgyZjBlMjBmYTQ3YmExY2U2OTE2YzhhNmFmNDRkMzE1Y2RkNDczMjgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQxOTI2Zjg3M2FiY2EzZjg1MDg0Y2RhOTI2ZWNlZjAwZDQxZGIwYjM0YjZiZWExM2U3MGM2NzM1NTg3ZDdhMjgifQ" + }, + { + "resource": "repo://src/cli/inventory.rs#L40-L45", + "version": "repo-lines-v1:sha256:838b0ae71ad8dbcb378ce730c13e0fbae3c7fbc6a25989534034b888553d86a4:eyJzZWxlY3RlZExpbmVDb3VudCI6NiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNzg5NTIxOGVlNjNmY2QwOTRiZTBiZDM4MWZlN2NmOGJhMDIyNmRkZGQxNzFiZjkyOWNjNjNmMjljZTQwNTM2YyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiM2NkYmMwM2JjMTNlODAyMmQzMzA3NmYwODJmMGUyMGZhNDdiYTFjZTY5MTZjOGE2YWY0NGQzMTVjZGQ0NzMyOCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZjllNzEwZTM5YTJlOWY2MzYxNWJkOGZjZDM1YWUzMmU2YzcxYmUyNDI1NzBjOWViYzVjMGQzNGRlZmQ5NjViMiJ9" + } + ] + }, + { + "id": "claim_1921729cc6974a51941be9ae23967215", + "statement": "db purge (handled directly in main.rs before any other dispatch) permanently deletes the profile's launcher.db plus its SQLite sidecars (-wal, -shm, -journal) only after the --yes-i-am-stupid flag; without it the command prints what would be deleted and exits 1, ~/.ssh/config and imported hosts are untouched, and OS keyring passwords are left orphaned.", + "evidence": [ + { + "resource": "repo://src/lib.rs#L70-L93", + "version": "repo-lines-v1:sha256:c6d09b2f202f7cde9a970c98f8d3b2830cd9fedd1bc53e15d3d7565ec0ebc474:eyJzZWxlY3RlZExpbmVDb3VudCI6MjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImIwN2JmYzEwMTI4ZjU4YTc0NDAwMjAzZTQ5MzZiZTU1Mjc5MGZkNmUyMmU5ZjU5ZTEwYjFkZDQ0M2QyNzIyYjciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE2MjJiNzAyOWRjYTAxNmJhZTI5ODZmNWU4MDI2YjNkM2E1ODYwNGM3NjM1ZGQ3OTRiYmQ0YTgyM2MwZWRlMDEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImVmYTY4ZGFjOGQxMjA5ODVkMGE0OWI5NTYzNjRhMmNlY2UyNGZkYmQ4YTJkMTY3ZjBjM2E3NzI1ZmNkMmIxY2IifQ" + }, + { + "resource": "repo://src/main.rs#L100-L148", + "version": "repo-lines-v1:sha256:f5607f28d07b77db07f9909dab76d6178cf8e6cad4a9b51b49d8b35d4c8d5b36:eyJzZWxlY3RlZExpbmVDb3VudCI6NDksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRjMDgwYjZhOTM3ZjkzODBkNDM5NGIxODEyNjllNzUxMDIxNGQwNWI4MGQ1NDQxYWNjZWFkNmU3NDJiMzIyMzQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQwYTk0YzYyYzRlNTVkYjQ2NzNhMGJmNzQ0N2QyNjI4NWQxNjNkZGFkOTA4YmViYmVmMGM5NDQ5MzhjYzNlN2QiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjU5ZDZhNTBhZGY4NTc1NzFmOTgwM2E5M2U0YjM3ODUxOTQ2Yzg5MGE1OGRjM2UzYjIwNTRmMjVmMWJkYjQxN2UifQ" + } + ] + }, + { + "id": "claim_3344d8f79ea648369b2579573c7ba96b", + "statement": "sshub --help reaches cli::help::print_command_help for the same command set is_subcommand accepts (plus theme), printing a scoped USAGE block instead of the global help; for exec only the part before -- is scanned so a remote command's own -h is not mistaken for a help request.", + "evidence": [ + { + "resource": "repo://src/cli/exec.rs#L257-L265", + "version": "repo-lines-v1:sha256:e858c5a1e4c394af2aac05d5fdaf0f9ff0122eb209ee7568af52103bd7a81b65:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiOTg5YjFjMjNhMTRhNDI5NmQ4MjAwNmZiYzFlZTQyYWJiYzQ4MmI4MDBkN2M2MGRlMjcwZDcxN2NiZGUzOGMwMCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMTllNzYzNTVjZjExOTk0ODBjM2Y5OTgzNmIxNDg2Yzg0MWQ4OTFmYWFkOTA0YzYwOGUwOTMwMjJkMzc1N2FlZSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMTAxM2I3MDc1YzJkOTEyNzE0YTNhNGNkYzJhM2E0ZDU2NzZlYjdmNTkzYzc1Y2FmNDVmZGE5N2U1MjVmNWRmMiJ9" + }, + { + "resource": "repo://src/cli/help.rs#L1-L33", + "version": "repo-lines-v1:sha256:6a75192c0a9f784fe41ae2625a09bffc19ac2ba12b20cc552b23077f3e24205b:eyJzZWxlY3RlZExpbmVDb3VudCI6MzMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImEzOWNmN2I4MDhlZGIyMWZhZmJlNzdkNmVhOTk5NWQ3Yjg1ZGI0NWY5OWM4NDE3ZTlkNTFjYWEzY2JjMzFmNjEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjNjMjkzM2IyZWVlMmY4ZWJjNzAxNjEyZjVhMGM3Y2Y0YzI1MGZkMmJiNjZmODEzOTg2OTllMjI1OGI3OWYzOGEifQ" + }, + { + "resource": "repo://src/cli/mod.rs#L47-L57", + "version": "repo-lines-v1:sha256:1e8da951a7bb83beee326815d744d5d135917db228a84eebba08f8174d408872:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImE1MmRiNDE4MzRmMWZhYzcxYzliZThkNWU1ZGZhZDc2Yzc4ZDUxOWY2MWU4YmQ4OGFhOGQ5MmU2YWYzZGQxMTAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImZmYjkzMjYwZGNhMGIzODM3NGI0ODU2M2JiOThhZmM5MzE3OWQzYmU4N2EyMDE0ZTRmYjk0YmNlMzFlMWY5NTgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImZkOTViMTgwZDAyZDdhNTNmNjcwMDRlOTM5Mzc4YmViNTA0N2YyZDZhNDVmNDViZDEwMDQ2NmYzODlhZjdhNzcifQ" + }, + { + "resource": "repo://tests/smoke/cli_commands.rs#L115-L138", + "version": "repo-lines-v1:sha256:2c4d9a31836b4635513bec9fde65f8011a27c7e674751b7b727dd91b30a5e498:eyJzZWxlY3RlZExpbmVDb3VudCI6MjQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQyY2Q0ZGI0OGEzNTBlOGYwYjQ3OTQ0YzQ2ZTNmZmRiYmExMzJkMjI5MGE5MWQ3MDEyYjU2ZWI0NmM4YWI0ZjYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImY0ODE5M2JmMjg3MGQ2ODNiMmQ5MzU4MzFlNDlmMDYxMWZmYjA1MzBjMzMwZmUzZmY1Y2RhZmM4NmMyMzdiNWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjkzNzNjMDRiZDRmMWYxMzVlN2U5MjRkZTdiMTU3MjdlMGUxZWYwODc1ZmYzNTI0ZWY2MjAyZTYwOGRiMzUxY2IifQ" + } + ] + }, + { + "id": "claim_d4b0416aab1547b9899604a9936d4bf1", + "statement": "identity add/edit take secrets only through --password-stdin (read from stdin with the trailing newline stripped, before the row is created so an empty piped password never leaves has_password=true) and normalize --private-key/--certificate path flags (tilde expansion, empty-to-None); agent-remove runs ssh-add -d (via crate::ssh::agent::remove_key) for the identity's key and audits the outcome via=agent.", + "evidence": [ + { + "resource": "repo://src/cli/context.rs#L165-L184", + "version": "repo-lines-v1:sha256:f2cfb03068190796070812c282afae63247e2f1bf8f5c28b34e3187cb64f4814:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkyNTdiMDQ2MGEzMWZiNDZlMDIwNjc3Mzk4NzQ3MDlmZWIxZjY2MzA0Y2JjNjhkYzE1NTExMGY5OWQzY2I5MGQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijc3ZmY0MTk4YWJmZWUxODcxYzFjYjA3M2IzYmVlYjI2MWNlNTBhYzM5OTVhYjc4YWJhODA5ZTVjNTc1ZTI1MDMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ" + }, + { + "resource": "repo://src/cli/identity.rs#L243-L285", + "version": "repo-lines-v1:sha256:d5df2e129650fdc5b489e7eace8f864e03ddb0aee0a965a2cec5e9b6c162d375:eyJzZWxlY3RlZExpbmVDb3VudCI6NDMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjdjYmU4N2NmZTBjZjZkYjIzM2RjNGY3Njk2YTI4NDIxYTcyNTg2NDBhZDQ4YjhlNDQ4MWJjOTczMjBlNDFmZDIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImIyYTM1YzM3YjNlZGI3Y2E3Yzk3MTM4ZGNjMDM4YTliNzc3OTVmZGQyZDZjZDY0ODEzMDQ2YWYwY2E4NGU3NzUifQ" + }, + { + "resource": "repo://src/cli/identity.rs#L97-L125", + "version": "repo-lines-v1:sha256:abb7e994b2d6968148db11656f099172d45037ec02188bbc4b03ad7e31b0ca2f:eyJzZWxlY3RlZExpbmVDb3VudCI6MjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImY5ODA4NzY5NWZkZDVjYTUzNmE2NTgyZWY2ZTIwOTBmMWFlMjM3ZTY0MzMxZGFlOTZmOWU0N2FhZmNmZmViNTgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImYzOWE4NGZhOTY1OTkxOThkNzhhNDEwNWFhY2YzYjg0ZGEwMzQzNjFhYmEwMTg1MGU1Mjc3NWE5YWE0NGIyOGYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNjZGJjMDNiYzEzZTgwMjJkMzMwNzZmMDgyZjBlMjBmYTQ3YmExY2U2OTE2YzhhNmFmNDRkMzE1Y2RkNDczMjgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjUyMWEyOWRmODA2NDg5NzNjNGVlZmNiMGNlYzczZGQ4MWZiYmJlNmM5MzI3NGUxMDRjNGQzNWNjYjY5MzBhOTkifQ" + } + ] + }, + { + "id": "claim_46d942ab167646c0b05dea7db62b22e6", + "statement": "host add de-duplicates a taken name (unique_host_name suffixes -2, -3, ...) and warns on stdout, stores an optional password through the password store keyed by host id, and honors --favorite by adding the host to the reserved favorites group; host rename --strict fails on a clash instead of de-duplicating.", + "evidence": [ + { + "resource": "repo://src/cli/host.rs#L284-L339", + "version": "repo-lines-v1:sha256:c5d4e56729129b956a6c48c74402f20fe2500a0e6a46e5089fef3a992bb15398:eyJzZWxlY3RlZExpbmVDb3VudCI6NTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkyNWFlNzMxYTZhZWFkMmY0Y2EwMGFjZDZkMmRlNmViMjVmN2U3Y2FjYzAxMGYwZWU3MGRjMDkyMjk2NTMwMmUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjY4NTNmOTVlNTczYjg4MGIyMzNmODEzODZlOGUzMDEyYzM0ZDQ4MTk0MmY5N2UzODk4ZWQ5NTI3MDRjMzcxNTYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImIzMTI1Nzc0ZjgyZGRlODgxNzdlNDNhYTNlMGRlNzg1NzYyMjZlZmM0OGI1M2Q1ZTYyYWQ0YmFjMjM3NGFlZDUifQ" + }, + { + "resource": "repo://src/store/hosts.rs#L598-L604", + "version": "repo-lines-v1:sha256:c014fcde326520ebfafd135eca44ec0d966d4cd0dd71933a106cf14b33825c52:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMjFjOGI1ODRlMzU4NTgwMjIyZmEyZWE1NWQ1Mjk4Y2RmNDlhNzU5NDNkNTU0ZTg3NjhkMmY5OTkzNWM0ZjMxNCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiYzVjZGVmODA4NWFlNzRhYjYzNmQ2NDU5ZjVjNmQ1ZmJmYjBkYjI2NGYxYjBkNzViYWM1MTljOWIzZjY3MTdhYyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNGE1NmE0MDUxODFiN2VlMjM4NTUwNDA1YjY3YWE4MGJmMTdlNzA1OTM0OTZjYTQyNmI2MDU3OGZjNzVjODU1NSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZDM0NGY2MjVjOTVkYjcxNDA5ZTNhZTJhMGQ1MDgyNDFlZWE3ZTY3NTYzODdkYjg0Yjc5NjJmNGI4NzY5NTczMCJ9" + } + ] + }, + { + "id": "claim_f819fd8d34fd4f69aedadc2b1141b4e1", + "statement": "Connection fields of ssh_config-sourced hosts are read-only: host edit refuses changes to them (both the CLI layer and the store guard this), materializing a launcher row first when a connection-field edit targets a legacy alias.", + "evidence": [ + { + "resource": "repo://src/cli/host.rs#L284-L318", + "version": "repo-lines-v1:sha256:982dd7a48d4cc3c9e70d8d5295ca31f77e93aed213e9dba3dae82ad4bcef91fd:eyJzZWxlY3RlZExpbmVDb3VudCI6MzUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkyNWFlNzMxYTZhZWFkMmY0Y2EwMGFjZDZkMmRlNmViMjVmN2U3Y2FjYzAxMGYwZWU3MGRjMDkyMjk2NTMwMmUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjhmMzU5NGVjY2Y3ODUzMWRkMWI2ZTY4Yjc0MWNhYWYyMjlmZmQyMzAxYjY2MDUxNDlkOTEzOTdmYmQ3N2ZhYWYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjY4NTNmOTVlNTczYjg4MGIyMzNmODEzODZlOGUzMDEyYzM0ZDQ4MTk0MmY5N2UzODk4ZWQ5NTI3MDRjMzcxNTYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI3YTA2ZmQxMjI0Zjk3NGMxMDA1NDZhNGNjN2U3NjFhMDI2YjgxMjUyNDdkNWQ2ODI3MDFlYWFkYWYyNjNiNGYifQ" + }, + { + "resource": "repo://src/cli/host.rs#L341-L355", + "version": "repo-lines-v1:sha256:3aa6657591bc12fb133dcbcefdfec8428f790414e6dce4e5020a5afc4b7e95af:eyJzZWxlY3RlZExpbmVDb3VudCI6MTUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA2ODQ0YTIwMjQ4ZGIzZmJkN2M2YjEzNDBlMGNjZjQ5MDIxMTZlNWE0ZTVlNTZkYTZiOTg0MzJhMDQxOGQxZGIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNjZGJjMDNiYzEzZTgwMjJkMzMwNzZmMDgyZjBlMjBmYTQ3YmExY2U2OTE2YzhhNmFmNDRkMzE1Y2RkNDczMjgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjJlYjRiMjA3ZDQyZDNkNDcwOTU4ODkwMDk4NDk1YzhiMjYwOGZhNGE5MGY5ODFkNTRhMDUyYmQyNzM4ZjkwYzQifQ" + }, + { + "resource": "repo://src/cli/host.rs#L368-L370", + "version": "repo-lines-v1:sha256:afa4d8db6b7eecda3563c9d0d83873553c8b02ab36e0b18e9190f8844f44de3f:eyJzZWxlY3RlZExpbmVDb3VudCI6MywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiOWEwMGNmNDYyYzk2Nzc3MzZlOWI1YTY1MmFkZTM5YjBhM2NiNzYzZDllZTc4M2FmMDljNjQ5MTU4NWYxYjdmNiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZjllYWJlY2QzNmQ3ZjM1ZTkwNGY4NWFlMzA5NjQ5N2MxZTA2M2U2ZTc1MmE4YzljMjFiYWI4NzVjYmNkY2MzMSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNGQwNzNmZTllNWFkYjYyNDkzYzk4ZTVkNDFhNTdjNzdjMTNjMWRiZGE0Mzg5MTg5MGViMTM5NzlmNWYyZTJiMCJ9" + }, + { + "resource": "repo://src/store/hosts.rs#L332-L334", + "version": "repo-lines-v1:sha256:9c7132265c406e32f1cb55b677c70cb2d08435c12ca2aaaa14c40c0b0c7f7350:eyJzZWxlY3RlZExpbmVDb3VudCI6MywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiZTk4ZGVhYWFkOTNkYjUwZjcxYTRjNmMxNDZmYWQwMDk3MGEzZTUxNTQ0YjRkMDFkODA5MjdjZjE5MDRmNDg5NCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNTUyMmM0MmNkMTljNmQwM2Q4MDhjNDUyNzIwNTUyODI1YjdlYTFjNWI0OTFiMjE4NTExY2FjNzQxNWNmOWQ5YiIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZDNjMzhiNGExZTExMjBjMThkZTkzYjBmZGFhZTk4ZmVmOGM0NDIzYjE5NmZmNDAwOTM3YTUwMTdlMmUyMDcxNSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiY2FkNmNhY2E2ZTc2MGE2MjNiNTdiMGU1YTY2MTQ1NTA0NzE0ZWFjZThhYTMxNzdkNmNiMDJkMTQ1NmQwZGUyOSJ9" + } + ] + }, + { + "id": "claim_c7736cef268a4302a1b5b1b15c759372", + "statement": "Adding a subcommand requires touching: src/cli/mod.rs (is_subcommand + run_subcommand; a theme-style database-free command instead needs its own dispatch arm in main.rs before bootstrap), a module under src/cli/, a help block in src/cli/help.rs, a section in main.rs::print_help, completions.rs TOP_LEVEL plus the bash/zsh/fish bodies (one test per shell asserts the generated scripts offer the subcommands), the man page, the README and this page.", + "evidence": [ + { + "resource": "repo://src/cli/completions.rs#L16-L64", + "version": "repo-lines-v1:sha256:619c425f23fcc3a5c90fad11c786e203d5153c44547583a708c86ed1aa5786ac:eyJzZWxlY3RlZExpbmVDb3VudCI6NDksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijg2YjNiZDM0MDllZDdhNDQ4MjkxMzcxNDJhZDNkMzdmNzk3YjBjMWVhYTYyZmUyNDZkYTA1YmU1YWQwZjAyY2EiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFiMDdiZjZiMDhjMzFjMzMwMWJkODA2NzllY2Q3NDNhODc3NTZlNTExYWE5OWJjOGY4YzY3N2YzNTU4Y2JjNjMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjIyMzZmNGUwOTFlNGM0MWE1MzFhYTY1NmQ2MDkzNjIxY2VjYTM4YzQ2ZjRhNzEzZDlhYzExYjQ1ZTA4MTRhNjUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjEyNzNmM2RiZTEwZTA4MDJiNjQyYWYxMzZhMWNhZGZjZDYwMGNhODUxZTgyZjA2ZTVkMWVlM2VmMjRjMzBmZmEifQ" + }, + { + "resource": "repo://src/cli/completions.rs#L508-L534", + "version": "repo-lines-v1:sha256:f326a4523c9b21e355f00c500ab9ba56195684d5622301cdf62fcbc1d4c363d9:eyJzZWxlY3RlZExpbmVDb3VudCI6MjcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAwZGU5NDIyNmI5MGNiMWM1NWNiZGE4YWZlNDljMWY5NWQ2YTY4ZDVhM2M4MTM4YTU0YzAwMzc5MDU3MTk1ZGUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImEwMGRiMDQwZjI5MDliZjk3OGZmMGE5NTZhNTVlODNjNjUzNDE4ZGU2YmI2OGY4MDQxZGE5NzZmOGQzZDIyNzAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjAzODMxNTQyMWI4ODY4MzljMjBiMDJlNGQ0ZjJmZDhkNTAyOGEzZjRiOWMzN2Q5NGQ1ZDgwMDg1ZTQxMDYzZmUifQ" + }, + { + "resource": "repo://src/cli/help.rs#L3-L33", + "version": "repo-lines-v1:sha256:911456601d87ae35a37f2eaec6c81c0f93caf225337da078e5b730c88fcb2b88:eyJzZWxlY3RlZExpbmVDb3VudCI6MzEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImYwNjU3MzdlMDcxZjlmYmNkZjU0YjhhZDM4ZTY2ZTRiNmYxZjlmYTFjMDFlZDZhZjlkOTY2NGQyMjJhOTY0MDMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjoyLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjMzYWM3NDFkZmZjMGViMmJkZjk3YjRjYTk3ZmFkZmY2NzEyMzI1ZmU3NjMyMzA2NmE5YmQzMzA1NzM5MTE2NzAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjNjMjkzM2IyZWVlMmY4ZWJjNzAxNjEyZjVhMGM3Y2Y0YzI1MGZkMmJiNjZmODEzOTg2OTllMjI1OGI3OWYzOGEifQ" + }, + { + "resource": "repo://src/main.rs#L150-L237", + "version": "repo-lines-v1:sha256:7927a89b4af9bfd3a565ca0d5e4ec71ace1baf7cb3a98ef84ccb602b2d95ce48:eyJzZWxlY3RlZExpbmVDb3VudCI6ODgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjYyYjgyZTEzYTVhMjRkNTc4MDVmMjljZGVjNDViZTIyNzhmNzAxMDBkODYzNzZhMmYwMjJkODNlYWMwZTAwN2YiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijg0ZTQyMDhjZDZmZmI4YzRhMzU0N2IyZjU0YWRhMTk5NzhjM2YzNjVlNzhmOTUwMmVmNTE0NWNkZDJhYTg1NDMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjM1ZjZmNDY2ZTU5Y2ZjMzA3MDNjNDVmNzJhZjY1ZGU1NGFjMzYzZDM1NGNjMWEzMzMwMzUwYWNkNDUyNDM0YzQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEifQ" + }, + { + "resource": "repo://tests/smoke/cli_commands.rs#L496-L534", + "version": "repo-lines-v1:sha256:1f267750428f7aab8ed90b785714c71cf68375f791858a622853d469a247f4ce:eyJzZWxlY3RlZExpbmVDb3VudCI6MzksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQyY2Q0ZGI0OGEzNTBlOGYwYjQ3OTQ0YzQ2ZTNmZmRiYmExMzJkMjI5MGE5MWQ3MDEyYjU2ZWI0NmM4YWI0ZjYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk3ZWQ3NzBlOTI5NzNjN2FmYmFiYTBjMjdiN2YxYTc3YjYyZWM5ODg5MTc2ZDQ1NzhmNTBiOWFlZDlmMDM4ZTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjIxNmRiMjg5ZTgxZmEwMWViYjQ3N2MwNTQxNjg1YjQ2NDRmNmY4YTZlNTlmODNkMDBkYzI3ZWE5M2I2Y2Y3NTkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjA3YmNmNjAzNDNmOTUyMWIwMjUyOTY4YjI3NmIxMjA3NzY2YTg3ZWVjMzMxZDkzYzBiM2RjYjQyMWU4ZTU5OWEifQ" + } + ] + }, + { + "id": "claim_3166dbaa66c34db5ba148dd3b7fa2bfe", + "statement": "CLI smoke coverage lives in tests/smoke/cli_commands.rs, driving the real binary via assert_cmd with SSHUB_DATA_DIR/SSHUB_CONFIG_DIR/SSHUB_SSH_CONFIG pointed at temp fixtures so tests are hermetic; theme tests additionally prove database-free dispatch by pointing the data dir at a regular file and asserting no databases are created.", + "evidence": [ + { + "resource": "repo://tests/smoke/cli_commands.rs#L1-L34", + "version": "repo-lines-v1:sha256:e68377e48a0311a3970e7a4469b9ac157884fafe27797800190ad19b93473ceb:eyJzZWxlY3RlZExpbmVDb3VudCI6MzQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjdhMzlhOTJhMWI1NWM2ODllMTAyMzhhYTg4YTgzMmE0M2JkM2IzMDRjZGFiMzAyM2ZlZmFlZTNjMDIzYjk4OWUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImZiNzFkMjU0YTBhYzliNTk3ODFkYzYyNjMwMGZkM2ZhODY0MmU2ZDlkZDYwOGRkYjY0MGJlZjQyYTVlYWE2MGYifQ" + }, + { + "resource": "repo://tests/smoke/cli_commands.rs#L368-L380", + "version": "repo-lines-v1:sha256:e05768f3811e02f4d51e626921cd5d88e3d25fd3003173201eef725f9650f15d:eyJzZWxlY3RlZExpbmVDb3VudCI6MTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQyY2Q0ZGI0OGEzNTBlOGYwYjQ3OTQ0YzQ2ZTNmZmRiYmExMzJkMjI5MGE5MWQ3MDEyYjU2ZWI0NmM4YWI0ZjYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImU1MjU3OWZhNTFhODM3YjNlOGJhY2I2ZmNlNzQzMjk0YTgxZDEwMmRhMDM4ZTQ2ZDRmMmVmMGIzYWQyYTI2YjkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImQzNmZiYzVkMzRjZjczZmM2MzYzNmI4ZDA4MjRmOTNlNmU0MmU3MWFmZGFiMGI3Yzg0MzFmODhlZDcxZjBjNjUifQ" + }, + { + "resource": "repo://tests/smoke/theme_public_api.rs#L1-L40", + "version": "repo-lines-v1:sha256:c2239111ac68e3b9509337f20a6a4c2015ef2732cbf6d68ceeb3bdb17ef6dfb3:eyJzZWxlY3RlZExpbmVDb3VudCI6NDAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkyMmM1ZTE3NDlkOWE3ODU4NmUyMzJmZmM0MWZlZGQ1MDFmNzhhZGM4OTgxYmE3OTcyMGMzNWZjYzdkZWI4MTciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImMyNTM2OWI1MjRhYjVjYjQ2YmMyYjVjNTE4ODU2MWYzZTkxNDVkNzk0ZTNiZmE5YjlhNzNiNDFkYjY4OTkwNjMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI3NWIzMTE1MTBlOGUxODljYmU2MTVlNGRhOTE4Yzg4MzJlOGM4MTQ0ZjI5NDRlM2I1YzhiYmRlMzg3NGQ5YTgifQ" + } + ] + }, + { + "id": "claim_6eb433b6145041f7b0e711bde5bcab4e", + "statement": "The askpass handoff works by re-exec: the session spawns ssh with SSHUB_ASKPASS_FILE plus SSH_ASKPASS pointing at the sshub binary itself; when main detects that env var it prints the staged secret (owner-only temp file, deleted on Drop) and exits before touching argv, so the TUI and CLI never process that invocation; the same mechanism serves headless connect and exec via resolve_pending_secret + AskpassSecret guards.", + "evidence": [ + { + "resource": "repo://src/cli/exec.rs#L117-L126", + "version": "repo-lines-v1:sha256:1eab22db0711474441c7c00c93542f75c06cef13707f8138bd3c9484f63a7c9c:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRkYzhkN2VmZDhlY2VmOTcyN2RlNGJiYzIxN2NiNGM5ODE2OTBlZDZkZTg5OWM1Y2EwZjg0ZTMwYWRlNjRkOWIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk0ZWIxNzAwY2JiYWYyMzc3OTM2YzQ5MmI3ZWE4NGM4ZTA0YTJlZjgxMzM2ZGY1MTA0YThjY2M0ZWU5YjY2N2IiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjI4OWIxM2M4ZjVjMTFkMTFiMDBhZjhhZTZjYWMxNjZiNzA4ZjI0MWIyNjlhMTdhZDM1M2NiZDc3YWJjMmU4MTEifQ" + }, + { + "resource": "repo://src/main.rs#L8-L13", + "version": "repo-lines-v1:sha256:401e8e4452ae833ce394a7c89ba3b92f224ae1dca6a0968817270105ca5a97d7:eyJzZWxlY3RlZExpbmVDb3VudCI6NiwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiY2NkNGQ2OWQxMTA0M2M2Y2Q5M2M0YmM2YTlhZDViNGU4MTFkODI3N2Q5MjQ1NWVlNmQxZTY2NjQwZTllYjUzZSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiYmJhYjg2NGFlMmY1YzE4ZGE5ZDE1ZGVmMDBiZWY5YjFmZDU5MjFjOGZlOWI4NDk4ZTBhNGRjYTgzNzgxNDRkYiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMWYyZTUyNzcwZDI4Yjk1YzY4ZjRlNDBhOTI5YzI1ZTRlNTdjNTZkNWFiMzZmOGI4YmU0NTMzNGMzOTdiYWUxNyJ9" + }, + { + "resource": "repo://src/session/askpass.rs#L103-L119", + "version": "repo-lines-v1:sha256:5526c149c27da24f52f71b5f3b0c84b8a372db5d3101cef1755df35326b7ae14:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFkNmY0MWNhNzg2MDBhMjBkMGRiZTI1YzE4Y2I0ZTVjMGNiMjE0NmYwMjkxMDM3MTRkZjk2Y2NhNDcxNDM3NWQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjdiYzk4ZDZiMGRjNDkyYzUwMTZkZjEwODA1YTNhNTRlYjljMTg0YTVhNjEzN2U3ZjRiZWNmYTBjMjIwNTUwZDAifQ" + }, + { + "resource": "repo://src/session/mod.rs#L226-L247", + "version": "repo-lines-v1:sha256:0770ace742d516ff2b97721da7d45c18fb770ab1b14a29dcfde85a09fffdd78f:eyJzZWxlY3RlZExpbmVDb3VudCI6MjIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI2YzRhODMzODY0MTM0ZDcwNGE4M2I4NGNhMDkwOGNmNjE0ZjNlNGI4OTU0ZDM0ODlhNzQ0ODkyYmQxZmJhNTYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImY4Y2JkNTg4YTdjYzNhM2Y0NmY2ODkyYjExNjk0OTMxMmUxYWVkOWY5ZjU4Njc1ZmE5YzFkM2NmZWMzMDgxNGQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImFlZjFlNjhkZjE0ZWMxOTVhZTAzZjkzMDQwZDQ3YjVkM2YxMTc4NmQ3ZTNjYjQ0MjNjZTNkNTE3MTM3NzViMjAifQ" + } + ] + } + ], + "verification": { + "by": "openwiki/0.5.1", + "at": "2026-09-11T12:46:43.817Z" + } +} diff --git a/openwiki/.claims/workflows/session-terminal.json b/openwiki/.claims/workflows/session-terminal.json new file mode 100644 index 00000000..2292a58a --- /dev/null +++ b/openwiki/.claims/workflows/session-terminal.json @@ -0,0 +1,354 @@ +{ + "schemaVersion": 1, + "pageVersion": "sha256:8730c5faf2ad06b63c8cb017232a6e781cc1463b41b4f6c42fc0362f10466229", + "claims": [ + { + "id": "claim_dcd27978885d440f809fbd4eaed44c1c", + "statement": "Session key encoding sends unmodified arrows/Home/End as SS3 sequences (`ESC O A`…) exactly when the remote's DECCKM application-cursor mode is on — the flag is read from the session's own vt100 screen right before encoding — and as CSI (`ESC [ A`) otherwise, while modified cursor keys always use xterm's parameterized CSI form; the emulator already tracked the mode, and the encoder honoring it is the 0.11.0 Midnight Commander fix.", + "evidence": [ + { + "resource": "repo://CHANGELOG.md#L625-L632", + "version": "repo-lines-v1:sha256:0a38ddf43d941beb8d64593f1d49fa2eda4471fb5a5125394a1763aff8519925:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNzA2YWFjNTYwNmJhODcxZWI0MzMyNGFhNjAwNDdmNWM2ZTg3NTQwZmIwOGQwYWY0MTcwMDhjNTA1MGI4YWI3YiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiODMyODI0MGJiNjcxMzk4NWMzZTBhYTgyYTI5ZmUxMGI4YjBiZjg5YjFiNDlmOWUzNjZkMDk5OGEyZWZjOTFkZSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiOTZiMzI3YzY0NDUwMGM5OTI4MjdjNmY5ZGU1OWQ1ZjUyODIzMWIxNWFmY2M5YjVmMzE0NWJkYzk3NzVhNTQ4YiIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMTQ4OTgzZGY3ZGEzMmY2NzUxZGI3NTUxZjU0YjI0OTVlMDI0ZjFmZjAyNzZjNzg4M2YyNjBlNTAxZDA3ZjE4ZiJ9" + }, + { + "resource": "repo://src/app/session.rs#L146-L153", + "version": "repo-lines-v1:sha256:b2eb6496ee194e87f1fdf10bccfb758c9726cceb855647c03f5f53df91ae11d5:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiODhlZDMzOGFjMmY5NDdhMTViMDcyMGFiMzZiNzUyYTQ5ZTJmNTM3ZTg0MTRkNDdhMTQzYWIxNzY5ZTZlMWZmNiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZTZiMDQ0OTE0Njk3MjJlODM2M2E5NGU0MDQ0NTM4ZTAxM2EyYTIxN2UzNmIxZTU2OGJhNjBhY2I3YjRjOGYyZSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiOTE2YTUzODA5NGNhMWJjOGVjN2ZkYzIzODAyYWVlZTU1MTczNWQ1MGZhMGI0NjM5M2MzN2Q1MTk2ZmY3N2YxZCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMTYyZWU1N2U0MWE4YzIwYWUwMTY0ZGYyNjE0MzlkY2M0OTlkMzUyMDgzNDE1MTUxNmQwNDdlOGRlYWU4YTQ2NiJ9" + }, + { + "resource": "repo://src/session/keys.rs#L334-L364", + "version": "repo-lines-v1:sha256:1f2cdccd51eda82f40ae5e714e1d70f34bb975c490ab0c8d708e9c0c66579d3c:eyJzZWxlY3RlZExpbmVDb3VudCI6MzEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkyYjVlZGY1OTQ5Y2ZlYTY3ODIyY2Y2OTExMzIzNjNkNjhkNmYxZTE2OWY1ZTBkNGY3YzE0ZTVmYTFlNmQ0ZTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijc0YWI5OWE2ZTNhYjJjNmQyODlhMTMyYmEwYzA2YzE3NTZjOTAzZTRkYWRhNGFlNGIxMWI1MzczNTZkZDhmYTEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjU3ZDVjNTljOWM0ZjkwMDkyOGQxZGJlMDE3NGQ2YmFlODhmNmMyODY0MjkzMTFlODViYWNhMGFkNmQxN2IwZTgifQ" + }, + { + "resource": "repo://src/session/keys.rs#L77-L96", + "version": "repo-lines-v1:sha256:77a72a5c5885afbf474010c67b0f0971257cc303b2c42088cc31c27c97f4e140:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjYwM2MzNzBiNGExNGM3MWVjMTc2ZTA3NzdkNTg5ZDE0ODhhN2IwZGVhNjJjNjdlZGM5ZTcwMjM0ZTdmMjc4ZDUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQxYjhmNDJlODBjNTUzY2Y2YjMzNmU5YmIzY2Y4MDI4ZjYwNjkxNjcyMjBmMmU2OTkwYzIyNzdjMTUzZWJjYzYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjU3ZDU4MzUxNzU5MjVlYTU1YWNhMDE1MjRkOTNjNTQwOGUyMzJlMTUxMmJkZGE5NGExMTE2MjA3MWMyM2Y4YTAifQ" + } + ] + }, + { + "id": "claim_44e6ef69e6b643d49cc382f81459f458", + "statement": "Forwarding a mouse event to the remote requires the remote to have requested mouse reporting AND be on the alternate screen (`keys::selects_locally`): a mouse mode without the alternate screen is treated as a leak (an app killed before sending `ESC[?1002l`) and selects text locally at the prompt; Shift inverts the decision either way — local selection over a full-screen app, or forwarding to an app that asked for the mouse while on the primary screen — and with no mouse mode at all the event always selects locally.", + "evidence": [ + { + "resource": "repo://CHANGELOG.md#L125-L137", + "version": "repo-lines-v1:sha256:16766d87fc1baf8ecb9d83e724bec3cb6fe6857f7d021347f0901ddf5135a4c2:eyJzZWxlY3RlZExpbmVDb3VudCI6MTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjhmMjIxMjI4YjE2MWFjMWViMDFhNTQxMGI5OTU4NjI2NzJjMDk2ZDgyMjMzMjFmZmZlYTgxZGE1M2JhYmQ0YjgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImM3ZmU4MzhjZWUyMjhjYWQxNGU2MzU4OWE0Mzg3MDMxZGNlZjY0YTdjYjYxZmViYWFmNzBmYzQ5ZDYyMDFjYzYiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjI4YjU2NTExMzAzY2Y3Nzc1NDJmZTNhMmIwYzg1MTdiOWI1ZTUxYzgxMWI5NzgzM2I4MjZjYTBmYzZlY2M2MTYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQ2OGE0MmQ4NDE2YTAyODlhOWM3YTBiYTQzNWY5OGM4MmFmMDJlYTQ5MGQ4MDhhYzBmYmIzNjhjZTcxOWJhN2QifQ" + }, + { + "resource": "repo://src/app/session.rs#L505-L558", + "version": "repo-lines-v1:sha256:2a82ded5d0b21027d9126837173baf6db81dbc1e97da088c468d23fa167e9204:eyJzZWxlY3RlZExpbmVDb3VudCI6NTQsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjRiZTExMjE4YjJhMjkxZmMyMWZjZGIzZmM0YWI0MTkwMDVlNTkxM2ZiYjFhMjQyNWQ4Y2RjNGU2MTAxYzE0YTQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk1MzY2OTA5NjA5ODU1MTc5MWU2M2U2ZGI2NWNlMzU4NzRiMDQxZTdmNmJlNzMzOWEzYTU5NWVlZTNkNzZiODYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQ2Yjg0NmRmYzhmY2RmNzNlNTZkYTY1YmJlZmQwYTUyNmYzYmMzYTI1ODM3MzU0ZDI0ZTJlNzQzNDk4Y2YwYWQifQ" + }, + { + "resource": "repo://src/session/keys.rs#L100-L119", + "version": "repo-lines-v1:sha256:6946680d6498395679895f4fbf45ce650468fb4a91b516e5d51df01cdfb5c967:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM2OTg2MmU0NGNhYTA1YTAwODZkYWVmMDMyZjdlMjkzOWQ0NjlmM2ExMjIzNTc1YmY3MmEzZjRiOGMxMmI3ODMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjU3ZDU4MzUxNzU5MjVlYTU1YWNhMDE1MjRkOTNjNTQwOGUyMzJlMTUxMmJkZGE5NGExMTE2MjA3MWMyM2Y4YTAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImFiYzQ4YTllYjMwMGNmODk3YjhjOWYwMDJiYmJlNWI1ZmY1MzExMzBhYjRkNGVmM2EwZGFmZWFhNDY4MGQzNWMifQ" + }, + { + "resource": "repo://src/session/keys.rs#L410-L450", + "version": "repo-lines-v1:sha256:5e4750eec89814ee7a9b56c4042e9fb21ad3db21693ab72dc344306632df36be:eyJzZWxlY3RlZExpbmVDb3VudCI6NDEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAyNzA5Y2I5MGJhMTU5MmMwOTU0OTkwZGFjZDhiODMyOTk3ZmZmNzhiODA4MWM1NzMxOTRkMjllNDUxMGY4Y2QiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImYyZDgyOGEwMzdhYzFjODE1MzViMWQ1NTAwODMyMzIzYmZiYjg2NmEzYTNmODdlN2E2OGMxM2EyYmFlNTZmZmYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjRjMjA3MzQzMmU2ZWQ5YzE2ZjY5ZjJhODNjZThhYTY2NGI5YzM5ZTczMWQ5NmM3YTQ1MjI1ZWRhMTI5ODliZGMifQ" + } + ] + }, + { + "id": "claim_ec7fe67d9f2540b89759041e9e969960", + "statement": "In the mouse-forwarding path, events on sshub's own chrome — session header row 0, rows at or past the PTY body's last row (the footer), or columns past the grid — are dropped before encoding, so a stray click on the app's header or footer never reaches the remote as an edge row of the grid.", + "evidence": [ + { + "resource": "repo://src/app/session.rs#L560-L570", + "version": "repo-lines-v1:sha256:482055ff3e4297d7df40f9241844a53ee757ab55c5a2a2ff4c9d90766e485022:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBhMTUxM2I5NzBkODgzYzgzYzgzNmIyNWU4YmQwMmJiMGE5YzU2MTc0OTYxMDIxMTc1M2JmOTcyMjNmZWNlMTYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjYzMzhmZDI4MWYxMDY0NzIxODQzZDM0ZjA3OGNjYzJjNGNjNjk4M2U1YzgyYmQ4MmJlZjk5MjYxZWQwNmNmMjAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoyLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjM3OTRkMDJhODEyMTk0NGQxNzZkMjY1ZTliYjljYzU5ZjZhYjg5MGJjYWRmOGFlZmRlYmRiYmI0ZjJlYTg0NDQifQ" + } + ] + }, + { + "id": "claim_9db0a50254d5447b8730531848b5a599", + "statement": "`encode_mouse` emits the wire format the remote requested — SGR (`ESC[<{button};{col};{row}M/m`, 1-based coordinates, real button code on release signalled by lowercase `m`) or the default encoding (`CSI M Cb Cx Cy`, each byte 32 + value) — with xterm button codes (left/middle/right 0/1/2, wheel 64/65, +32 on drag, modifier bits 4/8/16) and drops events the active mode does not want, such as motion under press-only modes and releases in press-only mode.", + "evidence": [ + { + "resource": "repo://src/session/keys.rs#L144-L234", + "version": "repo-lines-v1:sha256:e02441598e69e9a1397c7311b6154e31d3e2db2d2a370359a1b63451bf56db60:eyJzZWxlY3RlZExpbmVDb3VudCI6OTEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImMzYjk2MTk3ZDU3Y2FiYjg4MTBhNDczNjRlZDA3ZGZkOTVjNTlmZTE0NDY2Y2UxYmI1NGE5ZmFmMGZkYzE3ZmUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijg2ZmJkY2JhYjEyZmRjMTBmNTA2NTZiZjQ4MTY3NzNiNTVhZjNhOTM4M2VhYWU4ZjljOWRkY2YwYzg5ZDYxOTEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI1ZTlkNGZiMDY0OGNjMWMxM2ZlMjU5Njc1NGMxMzllYTgyOTg0NTFiYzMwYjcxMWE5ZjMwODQxYTNkZDJiNGUifQ" + }, + { + "resource": "repo://src/session/keys.rs#L472-L549", + "version": "repo-lines-v1:sha256:e2187d80a7100d1822fa34909889962c00f9387cd111fd18d6f6236a366e3658:eyJzZWxlY3RlZExpbmVDb3VudCI6NzgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkyYjVlZGY1OTQ5Y2ZlYTY3ODIyY2Y2OTExMzIzNjNkNjhkNmYxZTE2OWY1ZTBkNGY3YzE0ZTVmYTFlNmQ0ZTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ" + } + ] + }, + { + "id": "claim_9956a01afbdf4264a76b92593df9ca1d", + "statement": "On the alternate screen (Shift not held) a wheel notch becomes three arrow keys — xterm's alternateScroll, encoded per the remote's application-cursor mode — because that grid keeps no scrollback, and the session shows a once-per-session toast naming tmux's `set -g mouse on` as the switch that makes tmux scroll its own history; Shift opts out to sshub's own scrollback, and the hint fires on the notch rather than on guessing what is running.", + "evidence": [ + { + "resource": "repo://src/app/session.rs#L532-L550", + "version": "repo-lines-v1:sha256:0dc2cc3684567b18cb20d47e063cd60bd1cd8cdda88d3fed8951c9f5003fd63c:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImVmYWJkNTZjMGRhYmI3NTVmNWFjNjM0NzAxYThiYjk4YzQ5YmZkZjk4NzBiYzRlM2QzZmI3MzBmNzRkYmM0MGEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBjNjQ3OWNjMjMwNDQwZmQ0MWU4MjI0MjE4Njc5Njk4NTU5ZDI4OTNiZDY3YjQzODBhZmFiODExYjM4YTU1YWEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUyN2QxYTUxOGQ4YjI2MDI2YWFmOTQ0MjQyOWZhOTQ4ZGQ1YmVkY2Y3MzljYjgwYThmMjFhYWFiNzI1MzY2MTIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImVjYjdlM2JkZTJhNjczYjVlOGUwNWY2M2Q5ZjVlMzEyYTBhNzE1Njg0YjEzNTI4NDVjZjI4MDc2OWRiMjBmZGYifQ" + }, + { + "resource": "repo://src/app/tests/session.rs#L1056-L1115", + "version": "repo-lines-v1:sha256:1391dcbec4c87fbbc948677c0a7a2af4b4c1df55ced83750824b157857b8c571:eyJzZWxlY3RlZExpbmVDb3VudCI6NjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQyY2Q0ZGI0OGEzNTBlOGYwYjQ3OTQ0YzQ2ZTNmZmRiYmExMzJkMjI5MGE5MWQ3MDEyYjU2ZWI0NmM4YWI0ZjYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA5YmRlNzU4ZTkwMmJhNjFkYzI2ZDk1ODc3NmE5YmYzNDJjZjIwZjBhNWQ2YWRjNWZkNDQ4MTcyMGZiYjYyMGQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImIwMzA5YTlkYTgzZWY1NTEyODliMTU2MTQ3YTUyYjgwMmZjNjUzYjA1N2VlMTRlMDlhNzhmNjEwOTg5Nzg5YjgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEifQ" + }, + { + "resource": "repo://src/session/keys.rs#L121-L136", + "version": "repo-lines-v1:sha256:53880bee19b0c4c4ab51c2fc2f61ecfe7a0acf7d60c47c0cc90e8f2d8ba33360:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImZkY2QxMTI5YzIyMmFmNWUyMjU1NDMzMDRlOWY4OTI5MmJkZDE2ZDIwNjVjN2Y2Y2FkZjQ2MzZiNWUyY2Y0ZDciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjJkYTdlM2Q3NmNiMGIxNjdhZWIxMGE5ZTNkMmRhMzVmNjNmNmQ0NzRhODgwMDc4MThmYTkxNmFlMDk2MGI1NTciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRiNDMzMWU4OGM2NDU3NzljOWM0MWQ3ZjlkY2UyNDhhYTM5MDA4MTdlMzY1YzEzNmI2OTcwMDkzMmI5MWJjMzAifQ" + }, + { + "resource": "repo://src/session/mod.rs#L482-L499", + "version": "repo-lines-v1:sha256:0d70c9175e95076b2dba86a2582cdf82a24d017b3a8653bebe0a2844f711afaa:eyJzZWxlY3RlZExpbmVDb3VudCI6MTgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImZkMjJmZjVhNWI1Y2UwNTg5OGVmNDg0NjAxYjNkYzk5NzUwNGNjMDZlNGEwNGUzYmZmNzBlYTdlNGYwMWIzZDMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjU1MGEzNTc0MzE3ZDhhNDgwOWY5Njk0MzA1MTNiY2RjODgyYTRjOWQ4ZDI2NDhjZjg1ZWIyYmVlNGJiMDI4ZGUifQ" + } + ] + }, + { + "id": "claim_541a83f4a4e146d1a91bdeb59e84e85c", + "statement": "The wheel and PageUp/PageDown share one guarded scroll path: on the alternate screen PageUp/PageDown are suppressed and forwarded to the remote app that owns paging, and any local scroll goes through `scroll_with_selection`, which shifts a live selection by how far the view actually moved — so a wheel that cannot scroll (alternate screen) never drags the highlight across static rows, while on the primary screen the selection follows the view.", + "evidence": [ + { + "resource": "repo://src/app/session.rs#L107-L142", + "version": "repo-lines-v1:sha256:f8fa5051ebad101ff4b43da16f1e86d5478963515e2f6eeb7c18207d93fac8a3:eyJzZWxlY3RlZExpbmVDb3VudCI6MzYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM4ZmQ1MjM5YzBjNjNmNTZlZTEzYjg0MDkzYjZiNDZmMmEzNzQ0ZWZhODUyMTNjMTRkZDBlYWU5MGExMTc4ZTQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImRjOTFjNTk4YTc0YjgwNmUyZTQxMTZjNTdlZDBiNzg4NTFkNTNlZDBhMGU1NWYwZDhjNzRkNDAzZDQ5MDEyNGQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjkxNmE1MzgwOTRjYTFiYzhlYzdmZGMyMzgwMmFlZWU1NTE3MzVkNTBmYTBiNDYzOTNjMzdkNTE5NmZmNzdmMWQifQ" + }, + { + "resource": "repo://src/app/tests/session.rs#L996-L1051", + "version": "repo-lines-v1:sha256:4397204f47fa2b7bf77b4593c01622b3f62fb7cba20c5d011f043af4dc4bc03b:eyJzZWxlY3RlZExpbmVDb3VudCI6NTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjEzMGM1NmQyOWUxZjk2NzMxMmNhZjBjM2Q2ZGExN2Q1MzI2YTA1MTY2OGVmNWM1NDFhMmM0ZGRhNmNmYjhjMjEiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE1OTM3Njc5MWU2OTQ3NmY3YTA3MTg5NmRjMjViOTEzN2VmODQyM2UzZTk2MzQwY2NmYWMzMzIzZDQyY2E1MWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjEwODA4M2YxYmE2MDljNjdkNjIxMGExMDI4NTIzNTk5NGNhY2Y2YWE5NmYxZTQ2MWQ1NjgzNWUwMTJlYThkMDYifQ" + }, + { + "resource": "repo://src/session/mod.rs#L461-L480", + "version": "repo-lines-v1:sha256:78815140360afa157643cba5555b423f286203bd616b71c4f119594acb48c33f:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImJiYzFlOThkYWEyOTAzMWNjYzFkOWE3MjhlMGNjY2IzOTFlMGE5OGYyYThhMzhkNjIyNzgxMzJjNmU1YWFjMDUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6Ijk5OWJkZWZlZWFjYTYzNDBlMDI5MGRjODZlODg5Yzg5ZDVlMTg5YWE1YmE0NGI1ODViM2I3MDczNGY0YTYyZDgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhjZjcwODllNjhhZTMwMjlhNWMwNmRhZDlhOGY3MzU0NjQ0MmUwMzYxNDY2Yzk5NmE4YTFkMDBjNTUxNDdmZGMifQ" + } + ] + }, + { + "id": "claim_cebc6b2078a44dd680d920b613e72682", + "statement": "In-app text selection is anchored over the grid with signed viewport rows so a drag held past the top/bottom edge can autoscroll without the anchor being clamped away; on release `selection_finish` collects the span (walking the scrollback for rows now off-screen and restoring the view) and copies it to the host clipboard via OSC 52 with a `copied N chars` toast.", + "evidence": [ + { + "resource": "repo://src/app/session.rs#L512-L531", + "version": "repo-lines-v1:sha256:4b5e72d8a5d019e4a83690639a3259a5e16cd70a3e2d28ec497f4afd6758c13c:eyJzZWxlY3RlZExpbmVDb3VudCI6MjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImUzM2IwYTY3YzhjNWQ2ZGRjNjQzNWZkYWM0NmQ2ZmQ4YzVmMWQzYjg4OGM3ZWExNzkxNDFlNzg4NTFiZGQ1YjMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBjNjQ3OWNjMjMwNDQwZmQ0MWU4MjI0MjE4Njc5Njk4NTU5ZDI4OTNiZDY3YjQzODBhZmFiODExYjM4YTU1YWEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImIxMmM1ZWExNjYwNDIxMjY2NmU1ZjVhZTUyMDk1OWQyODIyNDEzODAwZTk0YWY4MzU4ZGFmYzA5ZTdmOWZiZWQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjlmNzhjMzhiMjBlYWZlMDhjNzBmNThkOGMxYTZkMTUxMTQ4YzRjNDI5NGE0OGEzNzQ3NWU3NDZmNDQ2MTYwOWQifQ" + }, + { + "resource": "repo://src/session/mod.rs#L307-L359", + "version": "repo-lines-v1:sha256:f0664501f2af5b0ed8ec04e3f829ea6833339941bd0eea13f0a56719a3d1a72c:eyJzZWxlY3RlZExpbmVDb3VudCI6NTMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImRmMjI1Y2VmNjdjNWJhNWRhZTE3ZDA5YjY0MTUzNzU4MmUzOTFkYTkxN2FjZmM3MGJmZjNjMmJmYjAwMTNhMWYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImJiYTExZjVhNmNjNTIwOWFjZjEwYWEzMGNhMWIzMGM2YTMyMzQzMTg2MmRlYzRiZTUxODM0OGQ5NTY1ZGQ5MjgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjVjODliOGM2YTZmN2JiMGZhNDI2NTU2NGRmNmI5ODQ0NDNjYTJjMzFkMGMzZTllOTA2YTI5N2QwZjc1NDNiODEifQ" + }, + { + "resource": "repo://src/session/mod.rs#L421-L448", + "version": "repo-lines-v1:sha256:126304d9619afd034e5d47cb6aabfbef963f35e334468c907740e9cff86a0c2d:eyJzZWxlY3RlZExpbmVDb3VudCI6MjgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImExNjBkMThiMzViZjdlZmI3YWIyNGU4ZDExYmVmOTkyNjMyNGM4MDQyNWU0NDBiZWViOWQxZjczNDY2MDJhODQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjMwZjI0NzQ2NThlNDk4YTNmZWJhM2JkYzM3NWFhMjQ0NWU3MjFiYjgwMmE3OWMyY2Y0ZGJhNTc3ZTA3YWU0MjYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRiYjMzZjU1NjZmOWVmN2E3OTEyY2YxZTYwNmUwNjNhNGFjYTg0YmUwM2YyODNlNjNjMDUxOGEwYjk5MjM4YzEifQ" + }, + { + "resource": "repo://src/session/mod.rs#L93-L109", + "version": "repo-lines-v1:sha256:aa079ade5cf959d50ce7f1cb610cc995737c12aa91d481d6a8d02c1b9c5f5fac:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2OGRhMzc0ZDg2NmIwYWExMWUxN2VmMmQyYzI0NTU5NzRlZmRhMDc1MWZhNzdlMzNkMzRiZDUxMTM0ZGExMTkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjlhMThjOGE1MWRlMTMyYmE3ZWI1ODg5ODcwYzU4MDU5ZTMxNGJlMGVmNmQwN2U5Y2VjNDE3MTYyOGUzMDY1Y2EiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImJhOTY2OGVhNzc0MTUxMWFlZWYwNzU4MThiMDJhYzNhNDhmNzdiYTI4OGZiZGZkMDkyZGFlMDJkYjdhM2ZkNTYifQ" + } + ] + }, + { + "id": "claim_320ea3a787f94906968d354b486ea7db", + "statement": "Because vt100 implements no terminal queries, the parser's `unhandled_csi` callback answers DSR 6 (cursor position from its own grid, reported 1-based and clamped to the grid size so vt100's pending-wrap cursor one past the right margin never reports a nonexistent column), DSR 5 (`ESC[0n`), and DA1 (`ESC[?1;2c`); the kitty keyboard protocol query (`CSI ?u`), secondary device attributes (`CSI >c`), and unknown DSR parameters stay unanswered on purpose, since silence is what tells a caller the feature is unsupported.", + "evidence": [ + { + "resource": "repo://src/session/parser.rs#L409-L504", + "version": "repo-lines-v1:sha256:7e2be2c4e44f09458c9f6e55b8b5f1d8652e0a597739bf40bf2151335d290166:eyJzZWxlY3RlZExpbmVDb3VudCI6OTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkyYjVlZGY1OTQ5Y2ZlYTY3ODIyY2Y2OTExMzIzNjNkNjhkNmYxZTE2OWY1ZTBkNGY3YzE0ZTVmYTFlNmQ0ZTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjBiMWY2ZmFhMTk3MjY1ODE4N2JjZjk0ZTMxYzZkOGFlYzFhNmI0YjIwNWZmMjdjOGE4ZTgxNjZlZDYyZmU4MTYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijc1NjMwODE4Njc0YTZkODkwMWZmZTVmMzkzNWIzZDAzYWQ5MGEyMTM0NmNmZTI4MDA1MzIzZGZmZWFiMWIxMDUifQ" + }, + { + "resource": "repo://src/session/parser.rs#L96-L151", + "version": "repo-lines-v1:sha256:3e5a93a1dce7e22ca19da37ac335210b8cd2b152cd81010cfbdb18b597e23d42:eyJzZWxlY3RlZExpbmVDb3VudCI6NTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjU0MGUxZWExZDYzYjg5NWViZjZlNDk3ZjBjMjFkYTMzMWY2MDViMTdlZGRkYzEyYmEwOGJlOWJiNjFlZWZlZjYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjI2YjVhOTNhODFhZjBjNDBiNzhmMTFlMjc4NWQ3NzlhNjg2MWYxZTE1YTJhZmFlOTY1YjY2YTEzNjcyYmY3NGEifQ" + } + ] + }, + { + "id": "claim_e276049a410e4905912089d80f350c10", + "statement": "Query answers accumulate in a 1 KiB bounded buffer and ride `ParserState::take_replies()` back into the PTY for every session, visible or not, because the asking application blocks either way: `drain()` runs `answer_terminal_queries` for each session, the visibility discard path (`discard_clipboard_writes`) explicitly leaves replies alone, and inside `drain` the answer pass runs after the auto-typed secret so a reply cannot be read as part of the password.", + "evidence": [ + { + "resource": "repo://src/session/mod.rs#L1502-L1522", + "version": "repo-lines-v1:sha256:1b3601114f09d5b4d2be870ef119572d62b8df3c7397033b4db336f3f5f483dc:eyJzZWxlY3RlZExpbmVDb3VudCI6MjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkyYjVlZGY1OTQ5Y2ZlYTY3ODIyY2Y2OTExMzIzNjNkNjhkNmYxZTE2OWY1ZTBkNGY3YzE0ZTVmYTFlNmQ0ZTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImYyZDgyOGEwMzdhYzFjODE1MzViMWQ1NTAwODMyMzIzYmZiYjg2NmEzYTNmODdlN2E2OGMxM2EyYmFlNTZmZmYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImFkOTlhZGUyOWE4NDhmNzFlYjJhNjQzODZjZDYzZWRhODMxMzE4ZDEyYWYzYmI0ZWEyYTZmMzEzMWJhNWJiNzYifQ" + }, + { + "resource": "repo://src/session/mod.rs#L574-L582", + "version": "repo-lines-v1:sha256:66c984213cae237eb6054a61e68483fb916be593e41af587cb9e8a22cd1e1db7:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNmUwZGI1MDA2ZTA3MjAxMzQxNGI5YTk1ZTllZmZhNTExOTVlYWI1MTcxOTJkZGQ3NWIxYTliNWY3MzIyNzhmNSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZTZiMDQ0OTE0Njk3MjJlODM2M2E5NGU0MDQ0NTM4ZTAxM2EyYTIxN2UzNmIxZTU2OGJhNjBhY2I3YjRjOGYyZSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZmNlOGQxMzdmYTQ0YWM2NjQ4YjNhNGE2ZjU2ZjUwYmFiYTY3NzM0MTQ1ZDA0NzRhNGU1NTU2OTdmZDI2YmVjOCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZTEyYTAwOWQyZjIyZmI3ZWFiM2I3NGRhZWEyZjU0YzIxZjY0ZjEwMDE3OGM3MzI0NmZhOTg4YzVmNzQxOTQ3MSJ9" + }, + { + "resource": "repo://src/session/mod.rs#L629-L635", + "version": "repo-lines-v1:sha256:ae8a00c1d5d87331298fad9471af9d811e997c305697f48012abe11d0f369740:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNWZmNDAwYzRiMWExODI2ZWZiMWYyOWUyMDAxNTUwYWY4OTQ0Mjc1Njk3NTFlYmU2MjEyMzk0MWQ5NjViZWE3NiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiNWE4OGUxZThlYzNkMThmMWFhN2FkMDIxZmFiZjQ3MWNhYWM1MDJlNWZmNDFmY2U5MzY1NTU0YzY5OWJhOWU5YSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiZjZlNjAxMDViMWJhYjQ4MTJkYzUwZmNlZTUwMzVlNTI4NzgxNmFlMDMxYzBkMjc3YTcyYTM5NzE3ODA4OGI1MCJ9" + }, + { + "resource": "repo://src/session/parser.rs#L15-L19", + "version": "repo-lines-v1:sha256:7d17138263066f55e2b9f7d0ae7eb6604fd5993de2f500b2423b851e3e1f19b0:eyJzZWxlY3RlZExpbmVDb3VudCI6NSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNTFmYTRmYzI2ZDljZWRjMDQ2YTgwYmU0ZGVlMWE5MTQ2NTI1MzRjNzk3MTAyYmNhZTlmOTBkOTMzOGQxOWJkYyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiMWJjYTBhYjAwMzcxMjFhMzMxNmI4NGQ0MTQ0YTJiYWNhMjM2MjVhNTM4MTZmZjQzYzU3MGZiOGI4ZThkYWRiOCIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZDIzYzg1NTYzYzY3ODc0MjczOTBjMGMzYzkyOThhYjJiMjkzNGIwMGU5NWIxMDUwODQyZmJmY2RlNWE1NDgwNSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiM2E1ZGFkMDIzMjQyOWM3OWEwYzQ4NmI4ZDE1ZGEzOWE0NmMwMmM2NGNhZDM1NDA4NTJiOWI0OTQwNjgwZDU1ZiJ9" + }, + { + "resource": "repo://src/session/parser.rs#L163-L169", + "version": "repo-lines-v1:sha256:a5ef4a88013ddee959bd4b46996e23c69964fc35011b64bd03729d9b22995c36:eyJzZWxlY3RlZExpbmVDb3VudCI6NywiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiMDFiYTQ3MTljODBiNmZlOTExYjA5MWE3YzA1MTI0YjY0ZWVlY2U5NjRlMDljMDU4ZWY4Zjk4MDVkYWNhNTQ2YiIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDg5M2Y0MGUzNzgxZWI4OTZjODJkMDQ2YTA4MDY3YTExZTM1Y2RmMmYxZGZhZjI2ZmVhOTBiOGRiNjY3NjQxMyIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZTBjMWMwOTM2OGM2Yjg1NDFhOTkzZDE0ZTc1N2ZhMGI5NzY3MTUyMzlhMzc5ZTYwMzQyZGEyYjIyNjczMjQ4OCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiYjg1NjRhN2EwNzQwM2Q0YzQ1ODI1ZTY0ODM4M2NhNjBiYjk1NDM4NzFiNjBkYTcyZDU2NWNkMjQ0N2Q1NTEwNiJ9" + } + ] + }, + { + "id": "claim_243f89095a6a41d4bf5253e902eac182", + "statement": "Answers written into the PTY pass a token bucket (`REPLY_BURST_BYTES = 4096` burst refilled at `REPLY_BYTES_PER_SEC = 256` bytes/s, capped at the burst) with whole-batch-or-nothing semantics, because the remote decides how often it asks and the answers share the bounded PTY write queue with the user's keystrokes; the bucket is explicitly not the freeze fix — the dedicated writer thread with its 64-entry bounded queue is what keeps a non-reading remote from wedging the frame loop.", + "evidence": [ + { + "resource": "repo://src/session/mod.rs#L1012-L1028", + "version": "repo-lines-v1:sha256:36cbcc8a4ddb6fcd80c032c3c2c945971516cceeea45919f8e58eef766bca617:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFmNWFkZTJmZTdhMWJmMTJkNjNjMzk0ZmNiMzZkNTE5OGQ5Y2UyNzU2OTA4NTc5MWUzNzhkY2U5NjcwMTE5YWIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBjYTBjM2FjMDIzMGI3OWYxZmE5OGE5NzAwZTE3YzBiM2RjNDViNmUxMWU5NzFlMDQ1NmQ3ODhjZmU1Nzk4MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjhlNjQ0YmY2OGM5YmVkNGYzNzZjYTAyZThkYTlmMGUzZWFjYmUxZjkzNmZiZmY5MjRiZWI4NmUxNzA5NGU0ODMiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImJkNzVlNDEzODdhOWMyMTViYTJkNzM2ODBhNThiNzE0NDM3Mzg4MDRiZjc0ZGYxYWRjNGM5ZjVhZmRlYTc2ZDcifQ" + }, + { + "resource": "repo://src/session/mod.rs#L596-L627", + "version": "repo-lines-v1:sha256:d731bad71bbce2df958b9a7c660ed2ef763c00b1a40e9dc136b4742e3f7b44a0:eyJzZWxlY3RlZExpbmVDb3VudCI6MzIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImFmOTQ3NzAxYTNkYmQ5ODA0MWFhMWE2OTY0YWZkZWVmZWNlMDk3NTQ5YjgwMWZlMjFhZDliMWQzNzIwZDQ4OTgiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjNiNzA2ZDk5MDIwMmRlMzY0MTQyNzQ4YjEwMWE0ZjAzYjkwYmE4Y2U4ZmY0MTQ4YWJlM2MwYzZlM2RmZDU4YzUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImViNTYyZDI2NTJhN2UzMTNmMWY0Mzg0NDgyZTc0YWIxNTg3MTI3MDcxMDk2MTcxNDM5MTI5YWYzYTk4ZjNhZDcifQ" + }, + { + "resource": "repo://src/session/pty.rs#L188-L203", + "version": "repo-lines-v1:sha256:7690a770dbd69f6c35d3aa8fa8796b2b584f4bc27f4ccce923a79fa10da1aa28:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU5MWMyOTk1NjMyY2Y1OGRlMGRkMTQ5YzZlOGE4YzE4ZDJmZTFjNDk2YWZjMjVjMmRjYjc0YWIxNjE5NTBkYjAiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImJlMTg5NzBmZjI2YTkwNTk1MzI4YmI4ZDVjOWM0YWE1NjU0Yjg5OGM5NTgwMTY1ZDFiZDIyNGU5OTkwYjY4ZDkiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjU5ZDI5NDY4MTFiNjk2M2ZlNjAyNjQyMWYxMDAzN2RjZmQyODcwNzhhMWRjYTIyNzIyMTgxNDM3OGRkZTlkODIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhjNjY3Mzc2ZWMyODhmNThkOThjMzUwMWJjMGM4NmViNDRkMzRmMmRkMDIxMjE4NzY1MzZlN2ZjMmFiNmVhYzgifQ" + }, + { + "resource": "repo://src/session/pty.rs#L21-L32", + "version": "repo-lines-v1:sha256:346ad20d0ff54f14b245c7fabcd0225396d2977b7c9fb2ba9d70b207d4497420:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM1MmVkMWQyN2QxNTdkYWNjZjExYTQ1YmRkMjkyNzk0YjBlNmMyYmJmNGFlOTk2ZDU0Y2I2ZmFkNGZjOWM2M2EiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU3MWQyZThiZWE5ZTcwODk0Y2I5ZmY3ZjFlMGMzN2M2Njg2OThlNWNhYWI0ODBmOTQzODcwYTI1MjgwMWVmNzkiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImE1Y2VkMjE4OTFlZTZjMjcyODlkOGQzZmRlYWJiZGViYTU0YzFmZjkzZWI4OTVhY2I0YmNiY2Y5OTlhMjBkYzAiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6Ijg4ZTNhMTdlZGM2MjFiZmFlM2RjMDllYTliZTFiYmMzYzgyNjU3NWViN2ExNWZlZGViNDBjZjk0YzMwNmQ1MGQifQ" + } + ] + }, + { + "id": "claim_1adf9c587e0a45e187baa505e013eef6", + "statement": "Only the session the user is actually looking at may relay OSC 52 clipboard writes to the host terminal: the frame-level pass runs once per frame before diagnostics are collected, gated on `session_is_rendered()` (which counts a session under the picker as visible) and on `clipboard.relay_from_pty`; every other session discards its queued writes and drop counters immediately, so a background tab can neither change the clipboard now nor replay an old write when it later comes to the front.", + "evidence": [ + { + "resource": "repo://src/app/session.rs#L215-L285", + "version": "repo-lines-v1:sha256:271c05eaa81cb0cf77900aecf8c38eedc8449d9af44c00e7a3697cec95db76af:eyJzZWxlY3RlZExpbmVDb3VudCI6NzEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQxOGNjYzZlZjBmYWFmMTk1YzFiZWU1YzQ0YjBkYjE4NjVlM2UzMDA4ZjA0YmQzNzY0ZDBlNmI1ZTU5NmUwZTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjYyYzhjODI2OTU2Mjk4OWQxZGM4M2E2ZDc3YTM3Yzk4NTE0MjEwNzZmNjdhZDA4MWM3YzQ0ZDJkODRhZTYyZWIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjI1NzY2ZjAzZDkyMTc1OTczNjNiNmJiMzk2NmRmYWM5OTUyZjNhOGM4ODBiZjYyZTRjNGQ4MmY0MTIwZGIxMTgifQ" + }, + { + "resource": "repo://src/app/tests/session.rs#L882-L994", + "version": "repo-lines-v1:sha256:55c062d6c266bfc723b49ec44b19fd74c2b6156416f3f1debd84d0e915385f70:eyJzZWxlY3RlZExpbmVDb3VudCI6MTEzLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiJmOWM5YTcwNzA0YmNmYmY2NmUwMzczNjc3ZGNkMGQwZWRlYjA2ZDNhYTNiOTQ3YjJmYmY1ZDhhNmYxODAzOGQ4IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0MTJjYTM0NWNjZjc1YmY5YzA4MDZiY2U2OTViZThkZTgwOGI3OTk4NDI1MWE3YTU0ZDIwMmNmNjEwMWRkNDUxIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiJhNTkzNzY3OTFlNjk0NzZmN2EwNzE4OTZkYzI1YjkxMzdlZjg0MjNlM2U5NjM0MGNjZmFjMzMyM2Q0MmNhNTFlIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiJhYTRkNzIxOWU0NjkzODY4YTBmMDQwZDJiMzU3NGZhMDFjYzdlMzIyOTMzZTk5NTliMWRkYTdmMWQ5NmY5ZDE3In0" + }, + { + "resource": "repo://src/lib.rs#L293-L347", + "version": "repo-lines-v1:sha256:6c4a1ced583aa8cc11360af032de5962ef2946d294e076586590332ee673b59a:eyJzZWxlY3RlZExpbmVDb3VudCI6NTUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk4YjRjYmIxMmYxMjA1ZDI4YWI0NTM3ZDhjMmU3MTE1Zjk4YmNmNjZmY2EwNDY4ZGYyODNjZWY0NTZiNGU2ZGIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImNhZmI4Y2E2NzIyNzRhMjhkNzgzY2EyOTExZGExMzRlYTY1ODY1ZmZhNDY1MmY1NGI3YTlhZmJkNWZmMmNkYjEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImE5MmMzY2FlM2UyMWJiMDVmMzJlNTVlYzBhYTgyNjhmYzg4MzA3NzFlMmNmOTdkZGU0NjgxNDY0YTk1NzdmZTMifQ" + }, + { + "resource": "repo://src/session/mod.rs#L629-L680", + "version": "repo-lines-v1:sha256:25b91f8e3862ec5d7dd01577915dce05816e19f14bbaedb501bccd2d54eb31e2:eyJzZWxlY3RlZExpbmVDb3VudCI6NTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjVmZjQwMGM0YjFhMTgyNmVmYjFmMjllMjAwMTU1MGFmODk0NDI3NTY5NzUxZWJlNjIxMjM5NDFkOTY1YmVhNzYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjVhODhlMWU4ZWMzZDE4ZjFhYTdhZDAyMWZhYmY0NzFjYWFjNTAyZTVmZjQxZmNlOTM2NTU1NGM2OTliYTllOWEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImY4ZmVmOWJmNGY2ZTEzYWMyNGE2ZDEyNTRiMjViZGFjMTczMGNlMmIzZmNhMGYwMjg5NzNjNjAyNmJiNzJhYzEifQ" + } + ] + }, + { + "id": "claim_b70ff0da61354210bde25ccac0d7fefd", + "statement": "The OSC 52 relay is narrow and visible: only non-empty writes are queued (an empty payload is a clipboard clear and is neither relayed nor counted as a drop, so a remote cannot wipe the clipboard); clipboard reads (`ESC]52;c;?BEL`) are refused by leaving `paste_from_clipboard` a no-op; payloads over 64 KiB decoded are dropped as `oversize`, writes arriving with the 8-entry queue full as `queue_full`, and both reasons are counted apart and named in the user-visible notice (`remote copied N bytes to clipboard`, `; dropped 1 oversized write`, `clipboard relay dropped …`).", + "evidence": [ + { + "resource": "repo://src/session/mod.rs#L1221-L1249", + "version": "repo-lines-v1:sha256:4cab448d437ed74dbf3350273b58ad2fbc7dc794c0c120b153b6defcb74b1f3a:eyJzZWxlY3RlZExpbmVDb3VudCI6MjksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2MWQ3ODQzZDFiZjI0OGI3Y2NiOTNkMGIzYjY1ZTdhZTFjZWM1YzI2OTA5NTE4YzAyODQzNjZlM2Q4Mjk0MGQiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImI3YmJiYWM5OWI4Y2UwNTJlZmU4NzMxOTc0YTU1YTc4ZWI3ZGNjNTE4MjkwYzZlMGM0NTQyZjlmOWQ0Y2Y5NjIiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRkYTdiM2RlNWNmNjdjZmE3ZTkzOWMxOWM2MjBmZGEzOTg1M2ViMGJiMGRkNDA5NzljMDYyYjIwN2E0NzM5M2YifQ" + }, + { + "resource": "repo://src/session/parser.rs#L283-L402", + "version": "repo-lines-v1:sha256:b60ef4a529e51966df8a70060f97cadd74206749eb3e4e770db056205888f1ea:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIwLCJmaXJzdFNlbGVjdGVkTGluZUhhc2giOiI5MmI1ZWRmNTk0OWNmZWE2NzgyMmNmNjkxMTMyMzYzZDY4ZDZmMWUxNjlmNWUwZDRmN2MxNGU1ZmExZTZkNGU1IiwibGFzdFNlbGVjdGVkTGluZUhhc2giOiI0ODkzZjQwZTM3ODFlYjg5NmM4MmQwNDZhMDgwNjdhMTFlMzVjZGYyZjFkZmFmMjZmZWE5MGI4ZGI2Njc2NDEzIiwicHJlY2VkaW5nQ29udGV4dExpbmVDb3VudCI6MywicHJlY2VkaW5nQ29udGV4dEhhc2giOiIxZGU4MmY4ODY3NWU3ZDYxYTQ0NTU3YzJhNzVlOTU0N2FiYWUwNjZmZDNkNzYzOWRjZmQ5Y2MxMTg2OWFiZWZhIiwiZm9sbG93aW5nQ29udGV4dExpbmVDb3VudCI6MywiZm9sbG93aW5nQ29udGV4dEhhc2giOiJkZTkyMTU5MzFmNDYzOTIzZTc0MmMyODg3MjI4YmEyZmY5MWE1ZWE2YjlhMjQzNmQxMWM4YmE3N2M1MWNjMmU0In0" + }, + { + "resource": "repo://src/session/parser.rs#L7-L95", + "version": "repo-lines-v1:sha256:68bef044a00df24d2be78e93e565ba7fc56c39e58b0a0db57f262c8326ba5e39:eyJzZWxlY3RlZExpbmVDb3VudCI6ODksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjU3OWUxNDkzYjAzMTE4YmVhOGExYTZlMmZjYmY0NTcyNGFiODZlYWFkZTM5OWFlMGY2NWUwMjYxMWM2NmI3NTMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQwNjczYmNlMDE4MTdkZjM2MjNkZTcwYmIwNmVlNmJhMDllYzNlMjg2YWM2MjgxYmE1NWU1YzM4NmQ1ZmQ3ZDEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjljZTkzMzhkOTFiMTgxZjNhNjg1ZmQ1MjY3NDA5ZWYxMzUxYjhkMTBmNjY5MjNhYjcwNGNmNTA2NWEzZDM4ODgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImZkYTQ1ZmU4YWE4ODc0NGEyMzFiOWMyY2I4YWYyOGFhN2Y1ZDlkZTQ1NzE5ZGM2ZDk0MjQzZWI0Y2Q3NTc4NGUifQ" + } + ] + }, + { + "id": "claim_b3b21f1cec8d440782874a352c420dd7", + "statement": "`src/osc52.rs` is the single OSC 52 framing point (`ESC]52;c;BEL`) shared by the PTY relay and sshub's own copy paths (stored-secret copy, panel selection, SSH-log yank); the selector is deliberately normalized to `c` so an X11 primary-selection (`p`) payload lands on the host clipboard, and vt100 has already validated the base64 alphabet before the payload arrives, which rules out ESC/BEL injection.", + "evidence": [ + { + "resource": "repo://src/app/util.rs#L314-L322", + "version": "repo-lines-v1:sha256:2e73ed7975b0142d087bede61a34c4f4c094a66464ba7395a92cc44de79fb48d:eyJzZWxlY3RlZExpbmVDb3VudCI6OSwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNTg2MmY1ODdlZTg5ZjBjMWE4YjhlNTZmYjY2MDJjZDA3ODQwZTJlMjk0MTFhN2RkN2YxYzg2MjliMWY2ZTE5ZSIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMTllNzYzNTVjZjExOTk0ODBjM2Y5OTgzNmIxNDg2Yzg0MWQ4OTFmYWFkOTA0YzYwOGUwOTMwMjJkMzc1N2FlZSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNWU4YWQxNDU4NGU1ZDVjZjI2NzU4NDM0MTkxYWFkYjQwYTlkYTdhMmIxNjk5NTNlZjNmYjgzYmM0ZGE1NmRhNyJ9" + }, + { + "resource": "repo://src/osc52.rs#L1-L41", + "version": "repo-lines-v1:sha256:3c7dcfa7dbdb5cd84160cdebf2dce19953bccb416a52614ff92b554453812694:eyJzZWxlY3RlZExpbmVDb3VudCI6NDEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImM4YzJiN2FlYmY0ZmY0ZjgxNmQyMzhhY2FiZDk5YjgxZjJkYTg0ZmI1YmFmMmI3NzZkYmIzY2YyNmYxODU0ZGUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjowLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImRhNWJlMjhlZTYyNDQzMjA4OGIyZjM4MzkyYzdlN2YwMTNhNDczY2Y5M2JhNzRiM2Y5NDU5YTI4MjAyM2YwMTQifQ" + }, + { + "resource": "repo://src/osc52.rs#L76-L113", + "version": "repo-lines-v1:sha256:5b634f0e01167078fed4e4e867ebeb9209a71990c23f9aaccdbd7255b2d4d7ea:eyJzZWxlY3RlZExpbmVDb3VudCI6MzgsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI1Y2Y1ZmMyM2YyYTZkMjNjM2VhNTFlNzJmNWU4OTZjNzU1NWZiYWEwNWNkNWIxZWExODJiZjk2MThhOGU4MWIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjJmOGIyYTAyMGQ5OTY3MTJhNDA5MDRiMDM3MTk1ODkxNWE1NTk5NzEyMjRiYzk0M2MwMzdkNTMzYzI4YWU4M2YiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjowLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUifQ" + } + ] + }, + { + "id": "claim_1642b7179a684fc5912361c9b15f1c12", + "statement": "`[clipboard] relay_from_pty = false` in config.toml drops every PTY clipboard write silently (no notice); the default is `true`, configs written before the `[clipboard]` section existed keep the default, and the section governs only what the remote may do — sshub's own copy shortcuts are unaffected.", + "evidence": [ + { + "resource": "repo://src/app/session.rs#L254-L285", + "version": "repo-lines-v1:sha256:88f419923551a418526f176eaa56920972b0a222ffcc6e1580ead8cca8d938e0:eyJzZWxlY3RlZExpbmVDb3VudCI6MzIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjViMTNkNjcyZWNmYWQ2MDM1ZmYxODE4OWU0ZWE4OTQwOTEyMTk0YmRlZjA0MGRhZGU3YTc5ZjJlYTM5ZWVmMWMiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjcwMmNjNzYzOTUxNzJlNDkwZGM2YjlhMWNhZGI0NmRmZjk1ZmZlZGYzNzlkZTRmZGU2YjNkMTdhYzY3ODJiYzYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjI1NzY2ZjAzZDkyMTc1OTczNjNiNmJiMzk2NmRmYWM5OTUyZjNhOGM4ODBiZjYyZTRjNGQ4MmY0MTIwZGIxMTgifQ" + }, + { + "resource": "repo://src/app/tests/session.rs#L980-L994", + "version": "repo-lines-v1:sha256:ef0b504083b0621fe864ce5c2b15152ac898954633df228c45ca79c0f4e37856:eyJzZWxlY3RlZExpbmVDb3VudCI6MTUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQyY2Q0ZGI0OGEzNTBlOGYwYjQ3OTQ0YzQ2ZTNmZmRiYmExMzJkMjI5MGE5MWQ3MDEyYjU2ZWI0NmM4YWI0ZjYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImQ4Nzg1MjMzMDdlMmU1YTE1ZjIzMmRkOTAxOTU0NjdhMjY5OGM1MDRmZTY1MTg1Mjc0ZmE2ZDkyYTY5MjVjM2YiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImFhNGQ3MjE5ZTQ2OTM4NjhhMGYwNDBkMmIzNTc0ZmEwMWNjN2UzMjI5MzNlOTk1OWIxZGRhN2YxZDk2ZjlkMTcifQ" + }, + { + "resource": "repo://src/config.rs#L746-L764", + "version": "repo-lines-v1:sha256:f1a86e072cf32606b24745b3a0ebcfd945e791958183dcbd3c4956daf6bd68af:eyJzZWxlY3RlZExpbmVDb3VudCI6MTksImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkyYjVlZGY1OTQ5Y2ZlYTY3ODIyY2Y2OTExMzIzNjNkNjhkNmYxZTE2OWY1ZTBkNGY3YzE0ZTVmYTFlNmQ0ZTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjIyNDFlM2YxMDA4YTNkNzkyNDI2NGE4MzQ0MmMyMjBiNmI3NmI3NDYwYmRjNmNkMTg5ZTE5NTVjMjU5MDZkYjUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQ5ZWMzYjNmYzljZWRmMDgyMDRiY2M0YWUwYTVmMTMwYzhkODEzYjgxNTQ0ZWQ0NGVhYWJjYTA4ODEzOTg2MzYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImEwMGRiMDQwZjI5MDliZjk3OGZmMGE5NTZhNTVlODNjNjUzNDE4ZGU2YmI2OGY4MDQxZGE5NzZmOGQzZDIyNzAifQ" + }, + { + "resource": "repo://src/config.rs#L96-L116", + "version": "repo-lines-v1:sha256:4645cd385fd05ec24256261397ce1403c3092210cd7098c7567d4f8fffdb21a1:eyJzZWxlY3RlZExpbmVDb3VudCI6MjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjAxYmE0NzE5YzgwYjZmZTkxMWIwOTFhN2MwNTEyNGI2NGVlZWNlOTY0ZTA5YzA1OGVmOGY5ODA1ZGFjYTU0NmIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImI0YTM2NWE5MGVjMTFlOTA0MzI3NTJjZjkzNzZlYzVlZDM2YjgwYmE1ZThlNjg1NGZjZjk4OTcwZDMwNDQ0NjkiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImYwMGExZTY4MzJkZjQ2MWNiM2Q5Yzc0MzA3YjQ2M2FlMTA1Nzc3YmVmYjExOWNiZTA0NTY0NjQxNjY5NTAxYTIifQ" + } + ] + }, + { + "id": "claim_e25cff03f7104980a78be39db1413bf5", + "statement": "Session logs capture the raw PTY stream — `Session::drain` appends inbound bytes to the enabled log before any relay decision — so the raw OSC 52 sequence can already appear in an enabled session log; the relay neither adds it nor removes it.", + "evidence": [ + { + "resource": "repo://src/session/mod.rs#L515-L531", + "version": "repo-lines-v1:sha256:3457028bf6a052fb6fd929202acb03cdb8295b2797d5c2adfb21bbb70dc599cd:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjEyMzA1MGZkY2YwNGIxYjNiYWFhMDBlYzkwZGI1Zjc0OGE2ZTQzMzMxY2I4MDcxZTE3YjIyYzA4MjIyYTc3NTciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjBjNjQ3OWNjMjMwNDQwZmQ0MWU4MjI0MjE4Njc5Njk4NTU5ZDI4OTNiZDY3YjQzODBhZmFiODExYjM4YTU1YWEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjQzMzk2ZjI2NDkxOWY3NjhlZmE2MGU2MjE5Njc5YjhkMTBkOGM2NjkxMTQ0Yzg2ZDQ0MjAwMjhmY2Y5ZDFhM2YiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjU1NzgxOTA4MWVlZjZhZTMwZjE3OGEyNTk3NTJlZTZkYjg1MmE5ZmQ0YmQ0YTUyNWI4MjY1MmZlMWIxMjAyMzgifQ" + }, + { + "resource": "repo://src/session/mod.rs#L637-L653", + "version": "repo-lines-v1:sha256:c3a95ece27f7166432805f5c39330fd2e85c559e43c12c69197cd23d1ab3c293:eyJzZWxlY3RlZExpbmVDb3VudCI6MTcsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjM1YTQyYzM4ZDhmNDQxMTY2NWZiNjEzNDFlYTY3ZmNiYTZmZDAxZDU1NzYwNDg4OWNjNmE0YWRlOWRlYzkxYTIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjFiNDcxY2E3MmI3YWNiMGMzYjg5YTE2NGI4NWI5MmZiMjA0YTJjN2Y2ZWNmMjg3Zjc2OWVkYzlmMTQ3NmJiMjIiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImNmNTJjMGFmNDlkMjUwYzYwZTg4OGNmOTRkNjkxNmVkMzAxZTA4ZmJmMTQ1MzQ2MWE0OTVlZjM2YjdmMjU3ZTciLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjhiMzJiNGY4ZmUwODJkZWZiZDU5OTEwNjhkMWU1NTJmMGQ3MTgzM2JkMjg4NTNmNDFkM2ZmN2ZiN2JmMTYxMjQifQ" + } + ] + }, + { + "id": "claim_9c83b99b7ed948939d612e56f3a6152c", + "statement": "The event loop drives the terminal surface once per frame: every session's PTY is drained so background tabs accumulate output, the OSC 52 relay pass runs for the visible session before diagnostics are collected, and queued input events are dispatched in one batch — keys to `handle_key_session`, mouse to `handle_mouse_session`, pastes to `handle_paste` which forwards the whole blob to the active session.", + "evidence": [ + { + "resource": "repo://src/lib.rs#L293-L347", + "version": "repo-lines-v1:sha256:6c4a1ced583aa8cc11360af032de5962ef2946d294e076586590332ee673b59a:eyJzZWxlY3RlZExpbmVDb3VudCI6NTUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk4YjRjYmIxMmYxMjA1ZDI4YWI0NTM3ZDhjMmU3MTE1Zjk4YmNmNjZmY2EwNDY4ZGYyODNjZWY0NTZiNGU2ZGIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6ImU2YjA0NDkxNDY5NzIyZTgzNjNhOTRlNDA0NDUzOGUwMTNhMmEyMTdlMzZiMWU1NjhiYTYwYWNiN2I0YzhmMmUiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImNhZmI4Y2E2NzIyNzRhMjhkNzgzY2EyOTExZGExMzRlYTY1ODY1ZmZhNDY1MmY1NGI3YTlhZmJkNWZmMmNkYjEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImE5MmMzY2FlM2UyMWJiMDVmMzJlNTVlYzBhYTgyNjhmYzg4MzA3NzFlMmNmOTdkZGU0NjgxNDY0YTk1NzdmZTMifQ" + }, + { + "resource": "repo://src/lib.rs#L450-L472", + "version": "repo-lines-v1:sha256:d9b3e516a111e1f5e18ad25c1b34b610dbcd243efa547bc474894e86c1bb3bfe:eyJzZWxlY3RlZExpbmVDb3VudCI6MjMsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijk4NDcxYTJiNjI5ZjA4YzBlOTdlZmY3YTE3Mjc4MDVjNTkyNzY2MDU3YjYyMjIwNDEyN2RjY2EwMGU4Y2ExYjIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjM1ZjZmNDY2ZTU5Y2ZjMzA3MDNjNDVmNzJhZjY1ZGU1NGFjMzYzZDM1NGNjMWEzMzMwMzUwYWNkNDUyNDM0YzQiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImY3ZDk0Y2VmNjNhNGI0ODc1NGI2YjlkN2MxNmU0ZDI3OGM2NGRjN2MwY2Q4MzM4ODA5NDA1ZGI5MWY3MGU2ZjcifQ" + } + ] + }, + { + "id": "claim_250bda2d9abb46c2a6ee8e88bfc1596c", + "statement": "A paste into an embedded session is forwarded as one blob, and `Session::write_paste` wraps it in `ESC[200~ … ESC[201~` only when the remote requested bracketed-paste mode (DECSET 2004, e.g. vim) — preventing per-line autoindent/comment continuation in editors — while a remote that never asked gets the raw payload so the markers do not leak in as literal text.", + "evidence": [ + { + "resource": "repo://src/app/mouse.rs#L8-L15", + "version": "repo-lines-v1:sha256:64ccf98c6c79f7e12254e61fbd576f225fde248174999dbab77a9fc245c99b98:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiNmZhYjBkY2FjOWZkOTRlOTAxMWE4MDIzMDBmNDIxNmYyNWUzMDg0MWNhODgyN2ZjYmFkYzlhZmE1MDRjZDBiZCIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiZTZiMDQ0OTE0Njk3MjJlODM2M2E5NGU0MDQ0NTM4ZTAxM2EyYTIxN2UzNmIxZTU2OGJhNjBhY2I3YjRjOGYyZSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiZTQ5YThkNzgzMTAzOGM4Y2VlOTY1ZTg0OWMyMWMwZWYwNDBhZDRkZmJmMzU1NTRlYTBhYjhkOTViZDZiZmQxZCIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiNjVlNjE0ODFhMDg2MzRlZDdkODc5NjE3MjhiZTllYTE0YTEzODQ3N2E1MTNkYTFkN2QwMDkwZTRhZjliMGM3OCJ9" + }, + { + "resource": "repo://src/session/mod.rs#L1301-L1316", + "version": "repo-lines-v1:sha256:0db1c62d1d9b10e100f4761af44f0f462d88b4a41746c77b39d57f7c097022b9:eyJzZWxlY3RlZExpbmVDb3VudCI6MTYsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjI1Y2Y1ZmMyM2YyYTZkMjNjM2VhNTFlNzJmNWU4OTZjNzU1NWZiYWEwNWNkNWIxZWExODJiZjk2MThhOGU4MWIiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImVmZTIxZjkzNDAzNDMzMDRmMzMyMzk0MzhhNzQ2YzMxNzczZTY2NDc1ZDc5Yzc1MWY0NDY0ZWNlNzRhMjI0ODUifQ" + }, + { + "resource": "repo://src/session/mod.rs#L923-L934", + "version": "repo-lines-v1:sha256:7450c04663e5dd6d7ab680c8e6de60215c7b2d95b355e35f6cfabeb984b2ed84:eyJzZWxlY3RlZExpbmVDb3VudCI6MTIsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImZmMTdkZjZlY2FkNmIxMjY2NGZiZGYzZGUyYTBmZGNmOTc0NGM2YTJiZGFiYTA4ZjQ2ODgwYTI3OTc4Y2FhODkiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImIyNTBlMmFiZGFhYTRlMzMwN2I4MWZmNzkwZjliMGJmNjNiYTFiNTkzNWQ3MzAyYjZmMDQ0Y2Y2M2YxMTNhYjEiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImI1MGViY2FjZDRlNDY2ZDQ2MTQ0MWQ0ZWYyYzFmYWZkYjhjMmIzYzFhYjc4NDBkZmQ0YWIyYzE5ZjA4N2M5YTgifQ" + }, + { + "resource": "repo://src/session/mod.rs#L951-L960", + "version": "repo-lines-v1:sha256:8b46f51af1ec846b3b506268d0722108ad2ad1d6cd6e7dc7146a5f3e8c7e898b:eyJzZWxlY3RlZExpbmVDb3VudCI6MTAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijg2N2MzNGYyYWQ2YzNkMTM5NTFlMTMwNDU5MjNiNTI2MzQzNWY0ZjJmNTM2MDc5OGIwNmMyNDY3NjdkYjk0M2EiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6Ijg4M2U4ODdmM2Q4MjY4M2E1NGI5YjkwMTgwZGM2MWQ2ZTdjNDUwZWI1MjBkNjE1ODMzNzAxZDFjZjc1NDJmZjciLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6IjE5ZTc2MzU1Y2YxMTk5NDgwYzNmOTk4MzZiMTQ4NmM4NDFkODkxZmFhZDkwNGM2MDhlMDkzMDIyZDM3NTdhZWUiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjIzODIyM2IxOTQwNzVhOGM1OTA5MTlhZDNhNDAxMjNkMDJhMGQzNjhkNjI1MzNkOTk3Mzc0YjFlODNjNmEwMDQifQ" + } + ] + }, + { + "id": "claim_c405545bdba84a939628d92210a6abc1", + "statement": "The surface is pinned by three test layers: parser unit tests (query answers and clamping, relay caps and drop counters), live-PTY regressions in `src/session/mod.rs` that spawn real `sh -c` children at 24×80 and prove the cursor-position round trip (`CPR<1;1R>`, or `CPR<1;80R>` after a full-width line) and that `drain` never relays on its own, and app-level tests at 80×24 via `test_app` in `src/app/tests/session.rs` for the relay gate and the alternate-screen wheel/toast; `tests/e2e/session_switcher.rs` drives real sleep children and renders through a ratatui `TestBackend` (120×40).", + "evidence": [ + { + "resource": "repo://src/app/tests/session.rs#L1056-L1115", + "version": "repo-lines-v1:sha256:1391dcbec4c87fbbc948677c0a7a2af4b4c1df55ced83750824b157857b8c571:eyJzZWxlY3RlZExpbmVDb3VudCI6NjAsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQyY2Q0ZGI0OGEzNTBlOGYwYjQ3OTQ0YzQ2ZTNmZmRiYmExMzJkMjI5MGE5MWQ3MDEyYjU2ZWI0NmM4YWI0ZjYiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjA5YmRlNzU4ZTkwMmJhNjFkYzI2ZDk1ODc3NmE5YmYzNDJjZjIwZjBhNWQ2YWRjNWZkNDQ4MTcyMGZiYjYyMGQiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImIwMzA5YTlkYTgzZWY1NTEyODliMTU2MTQ3YTUyYjgwMmZjNjUzYjA1N2VlMTRlMDlhNzhmNjEwOTg5Nzg5YjgiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjoxLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6IjQxMmNhMzQ1Y2NmNzViZjljMDgwNmJjZTY5NWJlOGRlODA4Yjc5OTg0MjUxYTdhNTRkMjAyY2Y2MTAxZGQ0NTEifQ" + }, + { + "resource": "repo://src/session/mod.rs#L1436-L1500", + "version": "repo-lines-v1:sha256:ef1afc5715f352e8e70b30fd3d2dd96303a848dd5e322bd32750ad0828168bc1:eyJzZWxlY3RlZExpbmVDb3VudCI6NjUsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6ImQ0MDI2NjQ4YWZmZjQ3NzUwMGIwMTU2MWNiOTA5Mjc5NWQzMDRkMTZlODA0NDUyZTc4Y2UzZmU0ZjIyOWFlZjciLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImYyZDgyOGEwMzdhYzFjODE1MzViMWQ1NTAwODMyMzIzYmZiYjg2NmEzYTNmODdlN2E2OGMxM2EyYmFlNTZmZmYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImM0MjBjMjJlY2ZjYTc2MzQ0YzY5MDAwZGU4MzdhNDMxZGFkOGFkMjEyYmM5MjM2ZjIyOGY5MDRlYjcyM2E2N2YifQ" + }, + { + "resource": "repo://src/session/mod.rs#L1502-L1522", + "version": "repo-lines-v1:sha256:1b3601114f09d5b4d2be870ef119572d62b8df3c7397033b4db336f3f5f483dc:eyJzZWxlY3RlZExpbmVDb3VudCI6MjEsImZpcnN0U2VsZWN0ZWRMaW5lSGFzaCI6IjkyYjVlZGY1OTQ5Y2ZlYTY3ODIyY2Y2OTExMzIzNjNkNjhkNmYxZTE2OWY1ZTBkNGY3YzE0ZTVmYTFlNmQ0ZTUiLCJsYXN0U2VsZWN0ZWRMaW5lSGFzaCI6IjQ4OTNmNDBlMzc4MWViODk2YzgyZDA0NmEwODA2N2ExMWUzNWNkZjJmMWRmYWYyNmZlYTkwYjhkYjY2NzY0MTMiLCJwcmVjZWRpbmdDb250ZXh0TGluZUNvdW50IjozLCJwcmVjZWRpbmdDb250ZXh0SGFzaCI6ImYyZDgyOGEwMzdhYzFjODE1MzViMWQ1NTAwODMyMzIzYmZiYjg2NmEzYTNmODdlN2E2OGMxM2EyYmFlNTZmZmYiLCJmb2xsb3dpbmdDb250ZXh0TGluZUNvdW50IjozLCJmb2xsb3dpbmdDb250ZXh0SGFzaCI6ImFkOTlhZGUyOWE4NDhmNzFlYjJhNjQzODZjZDYzZWRhODMxMzE4ZDEyYWYzYmI0ZWEyYTZmMzEzMWJhNWJiNzYifQ" + }, + { + "resource": "repo://tests/e2e/session_switcher.rs#L30-L37", + "version": "repo-lines-v1:sha256:fd6a17898b2bcc009a8106967c33fbbd137302a2b753051fd628484390537ae0:eyJzZWxlY3RlZExpbmVDb3VudCI6OCwiZmlyc3RTZWxlY3RlZExpbmVIYXNoIjoiYzRhMzczNzdhOTY4YjFmOWFhY2U0ZjMwZDk4MzZmMWI2NmU3NDA2MTkzNmMxYTI0Y2Y5NjkyY2UyMWRiZTYyNyIsImxhc3RTZWxlY3RlZExpbmVIYXNoIjoiNDEyY2EzNDVjY2Y3NWJmOWMwODA2YmNlNjk1YmU4ZGU4MDhiNzk5ODQyNTFhN2E1NGQyMDJjZjYxMDFkZDQ1MSIsInByZWNlZGluZ0NvbnRleHRMaW5lQ291bnQiOjMsInByZWNlZGluZ0NvbnRleHRIYXNoIjoiMTllNzYzNTVjZjExOTk0ODBjM2Y5OTgzNmIxNDg2Yzg0MWQ4OTFmYWFkOTA0YzYwOGUwOTMwMjJkMzc1N2FlZSIsImZvbGxvd2luZ0NvbnRleHRMaW5lQ291bnQiOjMsImZvbGxvd2luZ0NvbnRleHRIYXNoIjoiMmU1ZGY1M2ExZTA1MjRkZDBjMTQxNzQ1Y2EzZWEzMzViNzA2MDk3M2QzM2Y1ZDBmYWEwZDFjNDU3ZmUwMmJjZiJ9" + } + ] + } + ], + "verification": { + "by": "openwiki/0.5.1", + "at": "2026-09-11T12:46:43.817Z" + } +} diff --git a/openwiki/.last-update.json b/openwiki/.last-update.json index cdb84a28..4d8c34b3 100644 --- a/openwiki/.last-update.json +++ b/openwiki/.last-update.json @@ -1,8 +1,8 @@ { - "updatedAt": "2026-08-03T15:36:08.793Z", + "updatedAt": "2026-09-11T18:27:39.950Z", "command": "update", "gitHead": "6e0ef11e7b524df299b72cabd2c073a7fc133587", - "model": "openai/gpt-5.6-luna", - "status": "complete", + "model": "z-ai/glm-5.3-flash", + "status": "interrupted", "language": "en" } diff --git a/openwiki/.page-manifest.json b/openwiki/.page-manifest.json new file mode 100644 index 00000000..cf44ae8b --- /dev/null +++ b/openwiki/.page-manifest.json @@ -0,0 +1,82 @@ +{ + "schemaVersion": 1, + "pages": { + "/openwiki/architecture/data-model.md": { + "pageVersion": "sha256:1b2fff0cf6ba01402642dcd849e24063d2d277d6bc157b3cae6b0bb15a38fe91", + "completedBy": "openwiki/0.5.1", + "completedRunId": "c6abd07f-f9d5-48fd-99fb-818a0e9568ea", + "gitHead": "7c69f7e4242acc8ee9a717ad7e7f7edfd796ee99", + "sourceFingerprint": "sha256:804625b30f1391e8d147abca0466d4763a406c718f7dc94309a112e775b9ee13" + }, + "/openwiki/architecture/overview.md": { + "pageVersion": "sha256:cbec095865f03901034a81626fe09be505f6c6746c16a03a3315723e57f4edd7", + "completedBy": "openwiki/0.5.1", + "completedRunId": "c6abd07f-f9d5-48fd-99fb-818a0e9568ea", + "gitHead": "7c69f7e4242acc8ee9a717ad7e7f7edfd796ee99", + "sourceFingerprint": "sha256:804625b30f1391e8d147abca0466d4763a406c718f7dc94309a112e775b9ee13" + }, + "/openwiki/changelog-coverage.md": { + "pageVersion": "sha256:7f0ebcef8567975f150e98d747690aceb046192f2544ede43d0e7f78f719b863", + "completedBy": "openwiki/0.5.1", + "completedRunId": "c6abd07f-f9d5-48fd-99fb-818a0e9568ea", + "gitHead": "7c69f7e4242acc8ee9a717ad7e7f7edfd796ee99", + "sourceFingerprint": "sha256:804625b30f1391e8d147abca0466d4763a406c718f7dc94309a112e775b9ee13" + }, + "/openwiki/domain/hosts-identities.md": { + "pageVersion": "sha256:ae1b8961cc4017bec463dd5a8357882ed52f67148306abbd993a6210415ab0f3", + "completedBy": "openwiki/0.5.1", + "completedRunId": "c6abd07f-f9d5-48fd-99fb-818a0e9568ea", + "gitHead": "7c69f7e4242acc8ee9a717ad7e7f7edfd796ee99", + "sourceFingerprint": "sha256:804625b30f1391e8d147abca0466d4763a406c718f7dc94309a112e775b9ee13" + }, + "/openwiki/integrations/external-terminals.md": { + "pageVersion": "sha256:8074a69097ba96fa4937def252404db9d7a6dcbffa465127a746d3b66616263b", + "completedBy": "openwiki/0.5.1", + "completedRunId": "c6abd07f-f9d5-48fd-99fb-818a0e9568ea", + "gitHead": "7c69f7e4242acc8ee9a717ad7e7f7edfd796ee99", + "sourceFingerprint": "sha256:804625b30f1391e8d147abca0466d4763a406c718f7dc94309a112e775b9ee13" + }, + "/openwiki/operations/build-release.md": { + "pageVersion": "sha256:4abf5e74cce6bcf4b6a219080e3c7ec8eaf1618aaf51cd6cc3eb1fac2b4a629f", + "completedBy": "openwiki/0.5.1", + "completedRunId": "c6abd07f-f9d5-48fd-99fb-818a0e9568ea", + "gitHead": "7c69f7e4242acc8ee9a717ad7e7f7edfd796ee99", + "sourceFingerprint": "sha256:804625b30f1391e8d147abca0466d4763a406c718f7dc94309a112e775b9ee13" + }, + "/openwiki/operations/ci-cd.md": { + "pageVersion": "sha256:d9d09cd2a0cb35a2271b5a0b777266d80b53eec0823d9c84bca7a0118d3aceb8", + "completedBy": "openwiki/0.5.1", + "completedRunId": "c6abd07f-f9d5-48fd-99fb-818a0e9568ea", + "gitHead": "7c69f7e4242acc8ee9a717ad7e7f7edfd796ee99", + "sourceFingerprint": "sha256:804625b30f1391e8d147abca0466d4763a406c718f7dc94309a112e775b9ee13" + }, + "/openwiki/security/secrets.md": { + "pageVersion": "sha256:28fee48689374f42a2a1f48da108caac72ac9c743d0108262e524386154483f7", + "completedBy": "openwiki/0.5.1", + "completedRunId": "c6abd07f-f9d5-48fd-99fb-818a0e9568ea", + "gitHead": "7c69f7e4242acc8ee9a717ad7e7f7edfd796ee99", + "sourceFingerprint": "sha256:804625b30f1391e8d147abca0466d4763a406c718f7dc94309a112e775b9ee13" + }, + "/openwiki/workflows/broadcast.md": { + "pageVersion": "sha256:a8d0390b3dacfc2a7e1389973e8d97172b8ee98faea22da21530a5e9cb4d83bf", + "completedBy": "openwiki/0.5.1", + "completedRunId": "c6abd07f-f9d5-48fd-99fb-818a0e9568ea", + "gitHead": "7c69f7e4242acc8ee9a717ad7e7f7edfd796ee99", + "sourceFingerprint": "sha256:804625b30f1391e8d147abca0466d4763a406c718f7dc94309a112e775b9ee13" + }, + "/openwiki/workflows/cli.md": { + "pageVersion": "sha256:ea371a884989f7555bb2f101dd7d86a3dd2752545f1af09ecc851be3c182bb8c", + "completedBy": "openwiki/0.5.1", + "completedRunId": "c6abd07f-f9d5-48fd-99fb-818a0e9568ea", + "gitHead": "7c69f7e4242acc8ee9a717ad7e7f7edfd796ee99", + "sourceFingerprint": "sha256:804625b30f1391e8d147abca0466d4763a406c718f7dc94309a112e775b9ee13" + }, + "/openwiki/workflows/session-terminal.md": { + "pageVersion": "sha256:8730c5faf2ad06b63c8cb017232a6e781cc1463b41b4f6c42fc0362f10466229", + "completedBy": "openwiki/0.5.1", + "completedRunId": "c6abd07f-f9d5-48fd-99fb-818a0e9568ea", + "gitHead": "7c69f7e4242acc8ee9a717ad7e7f7edfd796ee99", + "sourceFingerprint": "sha256:804625b30f1391e8d147abca0466d4763a406c718f7dc94309a112e775b9ee13" + } + } +} diff --git a/openwiki/architecture/data-model.md b/openwiki/architecture/data-model.md index 3c3ca1ae..4a0e505a 100644 --- a/openwiki/architecture/data-model.md +++ b/openwiki/architecture/data-model.md @@ -1,9 +1,64 @@ --- type: Data Model title: Data Model & Storage — launcher.db, metadata.db, config, and hybrid hosts -description: SSHub persists state in two SQLite databases (launcher.db for managed hosts/groups/identities/tunnels/audit, metadata.db for legacy ssh_config hosts), a TOML config file, and merges managed hosts with read-only ~/.ssh/config aliases through the HostResolver abstraction. +description: SSHub persists state in two profile-owned SQLite databases (launcher.db at SCHEMA_VERSION 15 for hosts/groups/identities/tunnels/audit/snippets/log bookmarks, metadata.db for legacy ssh_config aliases), a per-profile config.toml plus themes/*.toml, and merges managed hosts with read-only ~/.ssh/config aliases through the HostResolver abstraction. resource: src/store/mod.rs -tags: [storage, sqlite, schema, config, hosts, architecture] +tags: [storage, sqlite, schema, config, themes, hosts, architecture] +verified: + - by: openwiki/0.5.1 + at: 2026-09-11T12:46:43.817Z +sources: + - id: openwiki-source-378cd6aef69dc530a15ddefa + resource: repo://src/app/hostlist.rs + - id: openwiki-source-e8c0db94ed2adcf662c346a3 + resource: repo://src/app/keys.rs + - id: openwiki-source-a43ecd76250acdab98621877 + resource: repo://src/app/mod.rs + - id: openwiki-source-904d23c980c588abe7a7a2a6 + resource: repo://src/app/sftp.rs + - id: openwiki-source-c71c4ea2254563fa367afa88 + resource: repo://src/app/theme_picker.rs + - id: openwiki-source-af2644667d0b644b5c92e850 + resource: repo://src/cli/theme.rs + - id: openwiki-source-2a737474d86fc75cc9d9694f + resource: repo://src/config.rs + - id: openwiki-source-faa81a0468b230404a8b1a1f + resource: repo://src/credentials.rs + - id: openwiki-source-0c12f9f256442fb9fb90951c + resource: repo://src/hosts/loader.rs + - id: openwiki-source-ed8bf05e307c6278442542c2 + resource: repo://src/lib.rs + - id: openwiki-source-b55a21a31ede1b56cd31a6a6 + resource: repo://src/main.rs + - id: openwiki-source-4cd3467ee6390c934f2ea410 + resource: repo://src/metadata/db.rs + - id: openwiki-source-d7d76e3f61266c25b6860f64 + resource: repo://src/profile/mod.rs + - id: openwiki-source-1177e0e7ee3933b36ebabc4f + resource: repo://src/ssh/export.rs + - id: openwiki-source-8c90e650e97e8c2db8feaa52 + resource: repo://src/ssh/host.rs + - id: openwiki-source-5b3673a353d43a16cf57dfdb + resource: repo://src/ssh/import.rs + - id: openwiki-source-661a6d64d056120219351adf + resource: repo://src/ssh/resolver.rs + - id: openwiki-source-ef7bbf76631c2cc5e3fe2e35 + resource: repo://src/store/hosts.rs + - id: openwiki-source-1ae354c1ebc2ddac5f66e8fc + resource: repo://src/store/identities.rs + - id: openwiki-source-9b50b5601aca6c80cfecf9fd + resource: repo://src/store/log_bookmarks.rs + - id: openwiki-source-b829ac603b13b9d0bcc902a4 + resource: repo://src/store/migrate.rs + - id: openwiki-source-5b73669f3eef941c822538ff + resource: repo://src/store/mod.rs + - id: openwiki-source-c9bbdb6598e3836ed3d2ce0e + resource: repo://src/store/types.rs + - id: openwiki-source-dd2928e8834ab3a7609c3bea + resource: repo://src/theme/manager.rs + - id: openwiki-source-e1a4b140ea9d82a1094f85f1 + resource: repo://src/theme/registry.rs +generated: { by: "openwiki/0.5.1", at: "2026-09-11T12:46:43.817Z" } --- # Data Model & Storage @@ -11,73 +66,317 @@ tags: [storage, sqlite, schema, config, hosts, architecture] ## Profile-owned storage Normal installs keep `state.toml` at the data root and put each profile under -`profiles//`. A profile owns its two SQLite databases, `config.toml`, -fallback credentials, session logs, and tunnel PID state. `ProfilePaths` is -resolved once before resource construction and passed to stores, config, -resolver, credentials, logging, tunnels, watcher, and CLI context. Profiles -retain stable IDs so renaming does not change credential namespaces. +`profiles//`. A profile owns its two SQLite databases, `config.toml`, the +`themes/` directory, fallback credentials, session logs, and tunnel PID state: -Directory overrides (`SSHUB_DATA_DIR` or `SSHUB_CONFIG_DIR`) select compatibility -mode and use the override directories verbatim without `profiles/` nesting. +```text +~/.local/share/sshub/ +├── state.toml # known profiles + last used (atomic tmp+rename, 0600) +└── profiles// + ├── launcher.db + ├── metadata.db + ├── config.toml + ├── themes/ # user theme files, beside the profile's config.toml + ├── credentials.json # fallback when the OS keyring is unavailable + ├── logs/ + └── tunnels/ +``` + +`ProfilePaths` (`src/profile/mod.rs`) is resolved once before resource +construction and passed down — stores, config, resolver, credentials, logging, +tunnels, watcher, and CLI context never rediscover paths from the environment. +Profiles retain stable ids so renaming does not change credential namespaces. +Every profile directory and database file is hardened 0700/0600 via +`src/secure_fs.rs`. + +Directory overrides (`SSHUB_DATA_DIR` or `SSHUB_CONFIG_DIR`, with the +`SSH_LAUNCHER_*` fallbacks) select compatibility mode: the override directories +are used verbatim, with no `profiles/` nesting, no `state.toml`, and an empty +credential prefix so existing installs keep their keyring keys. ## Two SQLite databases | DB | Path | Contents | |---|---|---| -| `launcher.db` | `profiles//launcher.db` | Managed hosts, host groups, identities, tunnels, auth events (audit), UI state | -| `metadata.db` | `profiles//metadata.db` | `host_metadata` for **legacy** ssh_config-only hosts: tags, description, environment, favorite, last_connected, session_logging, transport | +| `launcher.db` | `profiles//launcher.db` | Managed hosts, host groups, identities, tunnels, auth events (audit), snippets, log bookmarks, UI state | +| `metadata.db` | `profiles//metadata.db` | `host_metadata` for **legacy** ssh_config-only aliases: tags, description, environment, favorite, last_connected, session_logging, transport | + +The split is historical: `metadata.db` predates the launcher database and still +backs per-alias metadata for hosts that exist only in `~/.ssh/config`. +`LauncherStore`'s own docs admit the "MVP still uses MetadataDb" — treat the +overlap (e.g. `session_logging` and `transport` exist on both `ManagedHost` and +`HostMetadata`) as an acknowledged incomplete migration, not a design to copy. -The split is historical: `metadata.db` predates the launcher database and still backs per-alias metadata for hosts that exist only in `~/.ssh/config`. `LauncherStore`'s own docs admit the "MVP still uses MetadataDb" — treat the overlap (e.g. `session_logging` and `transport` exist on both `ManagedHost` and `HostMetadata`) as an acknowledged incomplete migration, not a design to copy. A best-effort one-way import from legacy `metadata.db` runs on fresh `launcher.db` creation (`src/store/migrate.rs`); a locked legacy DB must never brick launch. +When a fresh `launcher.db` is created (`schema_version` = 0), a best-effort +one-way import copies legacy `host_metadata` rows into `hosts` as +`source=ssh_config` rows (`src/store/migrate.rs`). It is deliberately tolerant: +a corrupt or locked legacy DB is skipped with a warning instead of aborting the +migration (which would roll back the schema and brick every later launch), and +a single corrupt tags blob degrades to no tags. ## launcher.db schema (`src/store/`) -`LauncherStore` wraps a `Mutex` (rusqlite, bundled SQLite) with `PRAGMA foreign_keys=ON` and `busy_timeout=5000`. Migrations use a custom `schema_version` table — base v2 schema plus stepwise `migrate_vN_to_vN+1` functions up to **SCHEMA_VERSION 13**, all in one transaction; column adds are guarded with `pragma_table_info` so re-runs are safe. +`LauncherStore` wraps a `Mutex` (rusqlite, bundled SQLite) with +`PRAGMA foreign_keys=ON` and `busy_timeout=5000`, seeds a "Default" identity, +and restricts the DB file and any `-wal`/`-shm` sidecars to owner-only. + +Migrations use a custom `schema_version` table — base v2 schema plus stepwise +`migrate_vN_to_vN+1` functions up to **SCHEMA_VERSION 15**, all in one +transaction; column adds are guarded with `pragma_table_info` so re-runs are +safe. The two newest steps: + +- **v13 → v14** — creates `snippets` (the command-snippet library). +- **v14 → v15** — creates `log_bookmarks`, keyed by `host_dir` + `file_name` + + `line` rather than a host row id, so a bookmark survives deleting and + re-adding the host. A migration test pins the #118/#123 merge order: a v13 + database jumping straight to v15 in one launch must run *both* new steps. -Tables: +### Table inventory -- `hosts` — managed hosts plus imported ssh_config rows; key columns: `source` (`launcher` | `ssh_config`), `ssh_config_hash` (drift detection), `transport`, `session_logging`, `os_icon`. -- `host_groups` — nested via `parent_id`; a `reserved` flag marks the built-in **Favorites** group (found by flag, never by name — a user's own group named "Favorites" is never repurposed). -- `host_group_memberships` — M:N; a host can belong to several groups at once. +- `hosts` — managed hosts plus imported ssh_config rows; key columns: `source` + (`launcher` | `ssh_config`), `ssh_config_hash` (drift detection), + `transport` (ssh/mosh), `session_logging`, `environment`, `username`, + `has_password`, `os_icon`. +- `host_groups` — nested via `parent_id`, may name a `default_identity_id`; a + `reserved` flag marks the built-in **Favorites** group. +- `host_group_memberships` — M:N join table; a host can belong to several + groups at once (the authoritative source; `hosts.group_id` is the kept, + synced primary group). - `identities` — seeded with a "Default" identity. - `tunnels` — tunnel definitions (see [tunnels](../workflows/tunnels.md)). -- `auth_events` — the audit log, including `log_path` for session-connect events when logging is enabled. -- `ui_state` — collapsed groups, ui_zoom. +- `auth_events` — the audit log (`host_name`, `username`, `via`, `status`, + `note`), including the `log_path` column for session-connect events when + logging is enabled. +- `ui_state` — key/value store for UI state: `collapsed_groups` (JSON array of + group ids), `ui_zoom` (with a legacy `name_zoom` fallback), and + `sftp_show_hidden`. +- `snippets` — reusable command snippets (name, command, description, JSON + tags); no host linkage, ordered by name. +- `log_bookmarks` — named jump-points into session log segments (`host_dir`, + `file_name`, `line`, `name`), newest first. +- `schema_version` — the single-row migration marker. + +```mermaid +erDiagram + hosts ||--o{ host_group_memberships : "membership rows" + host_groups ||--o{ host_group_memberships : "membership rows" + hosts }o--o| host_groups : "primary group" + hosts }o--o| identities : "connection identity" + host_groups }o--o| host_groups : "parent nesting" + host_groups }o--o| identities : "group default identity" + hosts |o--o{ tunnels : "tunnel target" + + hosts { + integer id PK + text name + text address + text source + text ssh_config_hash + integer has_password + text transport + integer session_logging + } + host_groups { + integer id PK + text name + integer reserved + integer parent_id FK + integer default_identity_id FK + } + host_group_memberships { + integer host_id FK + integer group_id FK + } + identities { + integer id PK + text name + integer has_password + } + tunnels { + integer id PK + integer host_id FK + text tunnel_type + } + auth_events { + integer id PK + text host_name + text status + text log_path + } + ui_state { + text key PK + text value + } + snippets { + integer id PK + text name + text command + text tags + } + log_bookmarks { + integer id PK + text host_dir + text file_name + integer line + } + schema_version { + integer version + } +``` + +*Entity relationships and key columns of `launcher.db` at SCHEMA_VERSION 15; `auth_events`, `ui_state`, `snippets`, `log_bookmarks`, and `schema_version` are standalone tables (`auth_events` references hosts only by name string).* + +### Favorites, and writes that look like ssh options -CRUD is split across `src/store/hosts.rs`, `identities.rs`, `tunnels.rs`; DTOs (`ManagedHost`, `HostSource`, `Tunnel`, `AuthEvent`, `NewHost`, …) live in `src/store/types.rs`. Secrets are **never** in SQLite — only `has_password` flags; actual secrets are in the OS keyring (see [secrets](../security/secrets.md)). +The reserved Favorites group cannot be renamed (a rename is a silent no-op) or +deleted. The migration that creates it never repurposes a user's pre-existing +group named "Favorites": if the name is taken, the reserved group is created +under a distinct name and found by the `reserved` flag. Runtime lookups are +flag-based: `favorites_group_id` selects `WHERE reserved = 1`, and the +connection-scoped helper additionally requires `name = 'Favorites'`. Membership +in Favorites is the source of truth for the favourite status — reads derive +`favorite` from reserved memberships, and writes sync `hosts.favorite` into a +Favorites membership row. -`sshub --profile NAME db purge --yes-i-am-stupid` deletes the selected profile's -`launcher.db` and sidecars (`src/lib.rs::purge_profile_database`); -`~/.ssh/config` is untouched and imported hosts reappear on next launch. Profile -credential namespaces prevent identical host names in different profiles from -sharing keyring entries. +`LauncherStore` refuses to store a `name`, `address`, or `username` starting +with `-` (`is_option_like` / `reject_option_like`, issue #101): ssh would parse +such a value as an option rather than a host, and addresses are written +verbatim from imports, so the write boundary is where a poisoned row is +rejected. `ssh::safe_ssh_target` remains the connect-time backstop, rewriting a +leading-dash target into the `ssh://` form OpenSSH refuses. + +CRUD is split across `src/store/hosts.rs`, `identities.rs`, `tunnels.rs`, +`snippets.rs`, `log_bookmarks.rs`; DTOs (`ManagedHost`, `HostSource`, `Tunnel`, +`AuthEvent`, `Snippet`, `LogBookmark`, `NewHost`, …) live in +`src/store/types.rs`. Secrets are **never** in SQLite — only `has_password` +flags; actual secrets are in the OS keyring behind `NamespacedPasswordStore`, +which prefixes every key with `profile::` so identical host names in +different profiles never share an entry (empty prefix in compat mode; fallback +`credentials.json` when no keyring — see [secrets](../security/secrets.md)). + +`sshub --profile NAME db purge --yes-i-am-stupid` deletes the selected +profile's `launcher.db` and its SQLite sidecars (`-wal`, `-shm`, `-journal`; +`src/lib.rs::purge_profile_database`) and refuses without the confirmation +flag. `~/.ssh/config` is untouched and imported hosts reappear on next launch; +keyring passwords are deliberately orphaned. ## Hybrid host model (`src/hosts/loader.rs`, `src/ssh/`) `load_merged_hosts` produces the unified host list: 1. DB hosts with `source=launcher` (full CRUD). -2. DB hosts with `source=ssh_config` — rows synced from the user's ssh config by `sync_ssh_config_hosts` (`src/ssh/import.rs`), keyed by `ssh_config_hash`, refreshed on `sshub sync` / import; launcher rows are never overwritten. -3. Remaining unresolved ssh_config aliases surface as `HostEntry::Legacy { host, meta }`, with metadata from `metadata.db`. +2. DB hosts with `source=ssh_config` — rows synced from the user's ssh config + by `sync_ssh_config_hosts` (`src/ssh/import.rs`): each existing row is + re-resolved and updated only when the resolved `ssh_config_hash` differs, + and a row that no longer resolves is left untouched. Launcher rows are + never overwritten. +3. Remaining unresolved ssh_config aliases surface as + `HostEntry::Legacy { host, meta }`, with metadata from `metadata.db` + (defaults ensured first). -Resolution goes through the `HostResolver` trait; `SshConfigResolver` (`src/ssh/resolver.rs`) parses `Host` aliases itself (following `Include` directives, depth-capped at 16) and shells out to `ssh -F -G ` for effective options. The user's own `~/.ssh/config` is never written — export renders to `exported.conf` with an atomic write and `.bak` (`src/ssh/export.rs`), and `conf_val` flattens CR/LF so a host field can't inject a `Host *` stanza. +Resolution goes through the `HostResolver` trait; `SshConfigResolver` +(`src/ssh/resolver.rs`) parses `Host` aliases itself (excluding wildcards and +negated patterns, following `Include` directives depth-capped at 16) and shells +out to `ssh -F -G ` for effective options. The user's own +`~/.ssh/config` is never written — export renders launcher-native hosts to +`exported.conf` (in the profile's config directory; `config_dir()/exported.conf` +in compat mode) with an atomic write and `.bak` backup (`src/ssh/export.rs`), +and `conf_val` flattens CR/LF so a host field can't inject a `Host *` stanza. -Hosts, groups, favorites, and identities as user-facing concepts: [domain/hosts-identities](../domain/hosts-identities.md). +Hosts, groups, favorites, and identities as user-facing concepts: +[domain/hosts-identities](../domain/hosts-identities.md). ## Configuration (`src/config.rs`) -`profiles//config.toml` (created on first run) sections: +The active file is the **profile's own** `config.toml` (created on first run; +in compat mode the config directory's `config.toml`). Sections: + +- Legacy `terminal` and `launch_command` keys may still occur in older config + files, but are silently ignored after the external-terminal launcher was + removed in 0.10.0. Sessions run on the embedded PTY (`src/session/`, + tui-term + vt100); see [integrations](../integrations/external-terminals.md). +- `[appearance]` — `active_theme` (theme id, default `default`), + `transparent_sshub_background` and `transparent_session_background` (the two + independent 0.14.0 transparency switches, both default off), plus + `show_detail_panel`, `date_format`, `disable_animation` (reduced-motion), + `confirm_quit`, `identity_columns`, and `os_logo`. The retired + `opaque_background` key is ignored and **dropped on the next save** — + recognized as a real key, not as a comment or a lookalike name, so a mention + of it never triggers a rewrite. +- `[clipboard]` — `relay_from_pty` (default `true`) controls whether the + visible embedded session relays OSC 52 clipboard writes to the hosting + terminal. +- `[session_logging]` — `enabled` (default off), `max_file_bytes` (default + 10 MiB), `retention_files` (50). +- `[tunnel_reconnect]` — `max_attempts` (12, 0 = unlimited), + `initial_delay_ms`, `max_delay_ms`, `stable_secs`, `jitter_ratio` (consumed + by `config::tunnel_backoff_delay`; see [tunnels](../workflows/tunnels.md)). +- `[keybinds]` — user rebinds from the Ctrl+K editor (`src/keybinds.rs`), + including the newer `logs_browser` (Shift+L), `snippets_manage` (Shift+S), + and `session_snippets` (Ctrl+N) actions. +- `[ssh]` — `config_path` overrides the imported ssh_config source for this + profile (`~` expanded). Resolution order: `SSHUB_SSH_CONFIG` / + `SSH_LAUNCHER_SSH_CONFIG` env overrides, then `[ssh] config_path`, then + `~/.ssh/config` (`profile::ssh_config_path_for_profile`). + +`save_config` deep-merges through `toml_edit`, preserving user comments and +unknown keys (an unparseable existing file is treated as empty rather than +blocking the write), then writes atomically via a 0600 `.toml.tmp` file and +rename. Loading triggers one-shot migrations (keybind migrations and retired-key +cleanup) that persist through the same merge so they run exactly once. + +### Every persist path writes the profile's own file + +`App::config_target()` (`src/app/keys.rs`) returns the profile's +`config.toml`, and every config persist path — settings toggles, the keybind +editor, forms, and the theme picker's `Enter` — funnels through +`save_config_to`, which writes that target (or the global file in compat mode). +This is the 0.14.2 fix: the theme picker used to write +`appearance.active_theme` to the global `~/.config/sshub/config.toml`, so the +setting appeared to take and silently reverted on the next profile-mode start. + +Env overrides: `SSHUB_CONFIG_DIR`, `SSHUB_DATA_DIR`, `SSHUB_SSH_CONFIG` (with +`SSH_LAUNCHER_*` legacy fallbacks), plus `SSHUB_DRY_RUN` / `SSHUB_AUTO_QUIT` +for headless runs. A staged-rename migration moves `~/.config/ssh-launcher` → +`~/.config/sshub` (and the data-directory equivalent) via a `.migrating` +sibling so a crash mid-copy can't freeze a partial config; symlinks are +refused outright during the copy. + +## Theme storage (`src/theme/`) + +User themes are `*.toml` files read from a `themes/` directory **beside the +profile's `config.toml`** in profile mode (`App::new_with_profile`), i.e. +`profiles//themes/`; in compat mode and for the headless +`sshub theme list|show` commands it is `~/.config/sshub/themes` (or +`$SSHUB_CONFIG_DIR/themes`, resolved read-only via `config_dir_path` so merely +listing themes never creates or migrates anything). -- Legacy `terminal` and `launch_command` keys may still occur in older config files, but are ignored after the external-terminal launcher was removed in 0.10.0. Sessions run on the embedded PTY (`src/session/`, tui-term + vt100); see [integrations](../integrations/external-terminals.md). -- `[clipboard]` — `relay_from_pty` (default `true`) controls whether the visible embedded session relays OSC 52 clipboard writes to the hosting terminal. -- `[appearance]` — opaque background, OS logos, quit confirmation, startup animation -- `[session_logging]` — `enabled`, `max_file_bytes` (default 10 MiB), `retention_files` (50) -- `[tunnel_reconnect]` — `max_attempts`, `initial_delay_ms`, `max_delay_ms`, `stable_secs`, `jitter_ratio` (consumed by `config::tunnel_backoff_delay`; see [tunnels](../workflows/tunnels.md)) -- `[keybinds]` — user rebinds from the Ctrl+K editor (`src/keybinds.rs`) +- A theme's **id is its file stem** (lowercase letters, digits, `-`, `_`); + built-in ids are reserved and a user file squatting one stays listed as + invalid instead of shadowing the built-in. +- The registry caps a file at **1 MiB** and reads at most **256** `*.toml` + files; discovery is one level deep. +- Loading is deliberately infallible: `App::load_themes_from` degrades a + broken themes directory to the embedded built-ins with a non-fatal notice, + and the degraded manager keeps pointing at the failed directory so a reload + after a repair works. A broken themes dir never blocks startup. +- A missing or invalid `appearance.active_theme` falls back to the embedded + `default` theme; the configured id (`saved_id`) is preserved and + `config.toml` is never rewritten, so repairing the theme file is enough to + get the user's choice back. -`save_config` deep-merges through `toml_edit`, preserving comments and unknown keys. Env overrides: `SSHUB_CONFIG_DIR`, `SSHUB_DATA_DIR`, `SSHUB_SSH_CONFIG` (with `SSH_LAUNCHER_*` legacy fallbacks), plus `SSHUB_DRY_RUN` / `SSHUB_AUTO_QUIT` for headless runs. A staged-rename migration moves `~/.config/ssh-launcher` → `~/.config/sshub` via a `.migrating` sibling so a crash mid-copy can't freeze a partial config. +The theme file format, picker, and CLI live in `docs/theme-system.md`. ## Change guidance -- Adding a column or table: bump `SCHEMA_VERSION`, add a `migrate_vN_to_vN+1` step, and prefer `pragma_table_info` guards — tests open in-memory stores (`LauncherStore::open_in_memory`) and run all migrations from scratch. -- In-memory tests point the launcher path into a temp dir so the legacy metadata import can't pick up a stray `./metadata.db` from the CWD. -- The [file watcher](overview.md#file-watcher) only reloads hosts; config.toml changes require restart. +- Adding a column or table: bump `SCHEMA_VERSION`, add a `migrate_vN_to_vN+1` + step wired into `run_migrations`, and prefer `pragma_table_info` guards. + Tests open in-memory stores (`LauncherStore::open_in_memory`) and run all + migrations from scratch; when a migration adds a table, add a rollback test + (drop the table, set the version back, re-run) so multi-step jumps — a v13 + DB landing directly on v15 — are covered. +- In-memory tests point the launcher path into a temp dir so the legacy + metadata import can't pick up a stray `./metadata.db` from the CWD. + +- The [file watcher](overview.md#file-watcher) only reloads hosts; config.toml + changes require restart. diff --git a/openwiki/architecture/index.md b/openwiki/architecture/index.md index 89e58d03..0e58fd3b 100644 --- a/openwiki/architecture/index.md +++ b/openwiki/architecture/index.md @@ -1,4 +1,4 @@ # Files -- [Data Model & Storage — launcher.db, metadata.db, config, and hybrid hosts](data-model.md) - SSHub persists state in two SQLite databases (launcher.db for managed hosts/groups/identities/tunnels/audit, metadata.db for legacy ssh_config hosts), a TOML config file, and merges managed hosts with read-only ~/.ssh/config aliases through the HostResolver abstraction. -- [Runtime Architecture — Event Loop, App State, and TUI](overview.md) - How SSHub runs — a synchronous 50ms event loop in src/lib.rs driving the App state machine (AppMode overlays + active_tab), the ratatui render pipeline in src/tui, and mpsc-based background workers for watcher, ping, SFTP, OS detection, and tunnels. +- [Data Model & Storage — launcher.db, metadata.db, config, and hybrid hosts](data-model.md) - SSHub persists state in two profile-owned SQLite databases (launcher.db at SCHEMA_VERSION 15 for hosts/groups/identities/tunnels/audit/snippets/log bookmarks, metadata.db for legacy ssh_config aliases), a per-profile config.toml plus themes/*.toml, and merges managed hosts with read-only ~/.ssh/config aliases through the HostResolver abstraction. +- [Runtime Architecture — Event Loop, App State, and TUI](overview.md) - How SSHub runs — a synchronous 50ms event loop in src/lib.rs draining session PTYs and worker channels each frame, the App state machine (38 AppMode overlays + injected AppDeps) with its privately owned theme manager, the ratatui render pipeline with three-pass theme backgrounds and tween/blit slides, and every background worker thread. diff --git a/openwiki/architecture/overview.md b/openwiki/architecture/overview.md index 57fdaf7d..7c2a9f16 100644 --- a/openwiki/architecture/overview.md +++ b/openwiki/architecture/overview.md @@ -1,71 +1,491 @@ --- type: Architecture title: Runtime Architecture — Event Loop, App State, and TUI -description: How SSHub runs — a synchronous 50ms event loop in src/lib.rs driving the App state machine (AppMode overlays + active_tab), the ratatui render pipeline in src/tui, and mpsc-based background workers for watcher, ping, SFTP, OS detection, and tunnels. +description: How SSHub runs — a synchronous 50ms event loop in src/lib.rs draining session PTYs and worker channels each frame, the App state machine (38 AppMode overlays + injected AppDeps) with its privately owned theme manager, the ratatui render pipeline with three-pass theme backgrounds and tween/blit slides, and every background worker thread. resource: src/lib.rs -tags: [architecture, event-loop, tui, ratatui, app-state] +tags: [architecture, event-loop, tui, ratatui, app-state, background-workers, themes] +verified: + - by: openwiki/0.5.1 + at: 2026-09-11T12:46:43.817Z +sources: + - id: openwiki-source-ca6cb4b1a14fd7969dfae3ec + resource: repo://CHANGELOG.md + - id: openwiki-source-93bf2f07a39425fb927e9324 + resource: repo://src/app/broadcast.rs + - id: openwiki-source-ecf23cb964455b5721cf8ef2 + resource: repo://src/app/connect.rs + - id: openwiki-source-e8c0db94ed2adcf662c346a3 + resource: repo://src/app/keys.rs + - id: openwiki-source-a43ecd76250acdab98621877 + resource: repo://src/app/mod.rs + - id: openwiki-source-485a289ed462dcfba5216dee + resource: repo://src/app/session.rs + - id: openwiki-source-c71c4ea2254563fa367afa88 + resource: repo://src/app/theme_picker.rs + - id: openwiki-source-bd0ba782351847bea0a88dab + resource: repo://src/app/tunnels.rs + - id: openwiki-source-7da0877d78263557d03ac935 + resource: repo://src/app/types.rs + - id: openwiki-source-9a4e39a3abc710e513d7a690 + resource: repo://src/broadcast/mod.rs + - id: openwiki-source-16aa81e530496bda7ce97299 + resource: repo://src/cli/host.rs + - id: openwiki-source-2a737474d86fc75cc9d9694f + resource: repo://src/config.rs + - id: openwiki-source-ed8bf05e307c6278442542c2 + resource: repo://src/lib.rs + - id: openwiki-source-b55a21a31ede1b56cd31a6a6 + resource: repo://src/main.rs + - id: openwiki-source-bc70e4d343b4dbde35d4da4f + resource: repo://src/osinfo/detect.rs + - id: openwiki-source-0710beba781aa96da63b7f37 + resource: repo://src/ping.rs + - id: openwiki-source-d7d76e3f61266c25b6860f64 + resource: repo://src/profile/mod.rs + - id: openwiki-source-bf6cda76234f60dd7eecaa7f + resource: repo://src/session/mod.rs + - id: openwiki-source-6ea4bd654665d0a13bbccee4 + resource: repo://src/session/pty.rs + - id: openwiki-source-83da07e99e9b1501c3666535 + resource: repo://src/sftp/worker.rs + - id: openwiki-source-dd2928e8834ab3a7609c3bea + resource: repo://src/theme/manager.rs + - id: openwiki-source-d94f38bf929e8f61ee58aff9 + resource: repo://src/tui/animation.rs + - id: openwiki-source-562799d7269608e05d2ccc52 + resource: repo://src/tui/blit.rs + - id: openwiki-source-344d0ae26e653accaaef1142 + resource: repo://src/tui/mod.rs + - id: openwiki-source-aa639fd6b014b97bcbbea7b3 + resource: repo://src/tui/theme.rs + - id: openwiki-source-d79be9aed145762bfeae5882 + resource: repo://src/tui/tween.rs + - id: openwiki-source-ccf8ed2726eb50df93a8ea99 + resource: repo://src/tunnel/mod.rs + - id: openwiki-source-116dbf37704b502041ac6b51 + resource: repo://src/watcher.rs + - id: openwiki-source-9de574c1d5cb882b489d71cd + resource: repo://tests/smoke/run_app_quit.rs +generated: { by: "openwiki/0.5.1", at: "2026-09-11T12:46:43.817Z" } --- # Runtime Architecture -SSHub has **no async runtime**. Startup first resolves one immutable profile -workspace in `src/profile/`, optionally runs the standalone picker, then builds -`App` with that workspace. Everything after startup is driven by one synchronous -event loop in `src/lib.rs` (`run()` → `run_app()`), with concurrency handled by -background threads communicating over `std::sync::mpsc` channels. +SSHub has **no async runtime** — no tokio, no futures; concurrency is background +threads talking over `std::sync::mpsc`. Startup first resolves one immutable +profile workspace in `src/profile/` (optionally running the standalone picker), +then builds `App` with that workspace. Everything after startup is driven by one +synchronous event loop in `src/lib.rs` +(`run()` → `run_with()` → `run_app_with()` → `run_terminal_loop()`). -## The event loop (`src/lib.rs`) +## Startup (`src/lib.rs`) -`POLL_INTERVAL = 50ms`. Each frame: +```text +parse global flags -> terminal -> intro splash -> profile picker (if several) + -> load profile config -> App::new_with_profile -> dashboard loop +``` -1. **Drain sessions** — every open embedded session's PTY is drained and resized; a `Connecting` session is promoted to `Session` on first output. -2. **Draw** — `terminal.draw` renders via `tui::render`. -3. **Drain input** — `poll_keys_and_watcher` drains *all* queued crossterm events per frame (the code notes that draining only one would make "paste into an embedded session crawl at ~20 chars/sec"), then non-blocking `try_recv` drains of every worker channel: - - config [file watcher](#file-watcher) → `app.reload_hosts()` - - ping worker (30 s interval, ring buffer of 30 samples per host) - - SFTP worker events → `apply_sftp_event` (see [sessions & SFTP](../workflows/sessions-sftp.md)) - - SSH probe logs, OS-detect worker results - - `tick_tunnels()` keep-alive (see [tunnels](../workflows/tunnels.md)) - - auth-events cache refresh (10 s) +- `run_with()` short-circuits to `Ok(())` when `SSHUB_DRY_RUN` (or the legacy + `SSH_LAUNCHER_DRY_RUN`) is set; `--dry-run` on the binary path is handled in + `src/main.rs` before the TUI is ever entered. +- The profile picker is a blocking terminal UI of its own: it runs only when + `stdout().is_terminal()` and no `SSHUB_AUTO_QUIT` is set; headless invocations + select the last-used profile silently. The picker plays the intro animation + only when the last-used profile has not set `appearance.disable_animation` + (`profile::picker_animation_enabled`). +- After `App::new_with_profile` resolves databases, config, themes, and workers, + `attach_config_watcher` spawns the [file watcher](#file-watcher-srcwatcherrs). + A watcher failure prints a warning and continues — hot reload is best-effort, + never a startup blocker. +- **Headless**: when stdout is not a terminal, `run_headless_loop` renders one + frame on a ratatui `TestBackend` (80×24) and then either applies + `SSHUB_AUTO_QUIT` or bails with + `sshub requires an interactive terminal (use --dry-run or SSHUB_AUTO_QUIT for + CI smoke)`. `SSHUB_AUTO_QUIT=q` sends the `q` key, confirms the quit dialog if + it appeared, and fails the run if `should_quit` was not set. Smoke tests drive + exactly this path (`tests/smoke/run_app_quit.rs`) — see + [testing strategy](../testing/strategy.md). -A headless variant (`run_headless_loop`) renders once on a ratatui `TestBackend` and quits — used by `SSHUB_AUTO_QUIT`/`--dry-run` and by smoke tests (see [testing strategy](../testing/strategy.md)). +The terminal session (`setup_terminal`) enables raw mode, the alternate screen, +mouse capture, and bracketed paste (so a paste arrives as one `Event::Paste` +blob instead of per-key events). A `TerminalGuard` restores all of it on drop, +and an installed panic hook disables raw mode *before* delegating to the default +panic hook, so a panic never leaves the terminal wedged. + +## One frame of the event loop (`src/lib.rs`) + +`POLL_INTERVAL = 50ms`. Each iteration of `run_terminal_loop`: + +1. **Size + session service** — read the terminal size; every open session is + drained (`Session::drain`, feeding the vt100 parser, the optional session + log, and the stderr debug tail) so background tabs accumulate output, and + *all* sessions are resized when the host terminal changed size (every tab + shares the same body area). Exited phases are logged once, connect-time + diagnostics are drained into the SSH log, and a fresh connect clears that + host's log first. +2. **OSC 52 relay** — `relay_visible_session_clipboard()` runs *before* + diagnostics are collected so a failed relay reaches the log in this frame. + Only the visible session may write the host clipboard; background tabs drop + their requests immediately rather than queueing them. +3. **Promote `Connecting → Session`** — once the active session's phase is + `Running` (first output), `app.mode` flips so Esc-cancel semantics change. +4. **Draw** — `terminal.draw(|frame| tui::render(frame, app))` (see + + [render pipeline](#render-pipeline-src-tui)). Mouse capture stays on + continuously so the scroll wheel always reaches sshub; native selection is + the terminal's own Shift+drag override. +5. **Auto-quit check** — under `SSHUB_AUTO_QUIT` the loop renders once and + exits via `apply_auto_quit`. +6. **Drain input + workers** — `poll_keys_and_watcher` (below). +7. **Quit** — if `app.should_quit`, `shutdown_all()` clears every embedded + session (`Session::drop` kills the child process group and joins the reader + threads) and the loop exits. + +```mermaid +flowchart TD + Start["Frame start: poll window 50 ms, or 16 ms while an animation plays"] --> DrainPTY["Drain every session PTY, resize all on size change"] + DrainPTY --> Relay["Relay OSC 52 clipboard writes from the visible session only"] + Relay --> Promote["Promote Connecting to Session once the active session is Running"] + Promote --> Draw["terminal.draw via tui::render"] + Draw --> Input{"crossterm events pending?"} + Input -- "yes" --> Keys["Drain ALL queued events: Key, Mouse, Paste"] + Input -- "no" --> Watcher + Keys --> Watcher["Config watcher rx triggers reload_hosts"] + Watcher --> Ping["Fold ping results into the 30-sample ring per host"] + Ping --> Sftp["Apply SFTP worker events for right pane and optional left-pane worker"] + Sftp --> Probe["Drain the ssh-probe channel into the SSH log"] + Probe --> Os["Persist OS-detect results, reload hosts"] + Os --> Broadcast["tick_broadcast: fold events, toasts, settle, audit once"] + Broadcast --> Tunnels["tick_tunnels: health check plus 2 s store resync"] + Tunnels --> Cache["refresh_auth_cache every 10 s"] + Cache --> Slides["detect_tab_switch arms and retires slides"] + Slides --> Quit{"should_quit?"} + Quit -- "yes" --> Stop["shutdown_all and exit the loop"] + Quit -- "no" --> Start +``` + +*One event-loop frame as implemented in `run_terminal_loop` + `poll_keys_and_watcher` (`src/lib.rs`): session drain → draw → input dispatch → worker drains → tick services → animation arming → quit check.* + +### `poll_keys_and_watcher` — input and per-frame drains + +The poll window is 50 ms at idle but **16 ms while `App::animating()` reports +any in-flight slide, morph, fade, or scroll chase** — the loop redraws at +~60 fps during motion and idles at ~20 fps otherwise. Then, in order: + +- **Input**: when an event is ready, the loop drains *everything already + queued* (`event::read` inside a zero-timeout poll loop): `Event::Key` → + `app.handle_key`, `Event::Mouse` → `app.handle_mouse`, `Event::Paste` → + `app.handle_paste`. The code notes the reason: one event per 50 ms frame + makes pasting into an embedded session crawl at ~20 chars/sec. +- **Config watcher** (`watcher_rx`): collapse the burst into one + `app.reload_hosts()` — the hot-reload feature. +- **Ping worker** (`ping_rx`): fold `PingResult`s into a per-host ring capped + at 30 samples (`crate::ping::PING_UNREACHABLE` = `u32::MAX` is the sentinel; + a trailing unreachable entry is cleared when the host recovers). +- **SFTP workers** (`sftp_rx`, `sftp_rx2`): events are collected first, then + applied (`apply_sftp_event` / `apply_sftp_event_left`) because applying needs + `&mut app`. The second pair exists only while the left pane browses another + server. +- **SSH probe channel** (`probe_rx`): drained into `app.push_ssh_log`, but it + is a **retired seam** — nothing sets `probe_rx` any more. The old 60-second + `ssh -v` prober was removed because it buried the events users care about + under hundreds of probe lines; the SSH log is now fed only by user-initiated + events (connects, session exits, key pushes). Clearing the SSH log drops the + receiver. +- **OS-detect worker** (`os_detect_rx`): collected then applied — + `apply_os_detect` persists the detected `os_icon` and reloads hosts. +- **`tick_broadcast()`** — drives the live fleet run: drains worker events, + folds them into the result table, spawns error toasts, arms the settle + countdown when every row is terminal, and writes the audit trail exactly once + (guarded by `audit_written`). +- **`tick_tunnels()`** — one-shot auto-connect bootstrap, health check + (`TunnelManager::check_health` via `try_wait`, stabilizing spawns after + `stable_secs`), keep-alive reconnect scheduling, and a **2-second resync** + (`tunnels_synced`) that re-reads the tunnel list from the SQLite store because + another sshub window or the CLI writes the same file. +- **Selection edge-autoscroll tick** for the active session's drag. +- **`refresh_auth_cache()`** — refreshes the audit cache every 10 s, preserving + the audit tab's current filter/range instead of clobbering it. +- **`detect_tab_switch()`** — the animation arming step, deliberately **last**: + it must run after the background drains so a mode change arriving from a + worker event (e.g. an SFTP `ConnectFailed`) stamps `mode_entered_at` in the + same tick — otherwise the next frame renders the popup at rest (a center + flash) before the open slide starts. It compares per-tick mirrors + (`anim_prev_tab`, `anim_prev_mode`, `anim_prev_audit`, …) against current + state to arm tab-switch / popup / session slides, and retires finished ones, + freeing their snapshot buffers. ## App state machine (`src/app/`) -`App` (`src/app/mod.rs`) holds all UI state plus injected dependencies (`AppDeps`), which are the main test seams: +`App` (`src/app/mod.rs`) holds all UI state plus injected dependencies +(`AppDeps`), which are the main test seams: -| Dep | Trait | Production impl | +| Dep | Type | Production impl | |---|---|---| -| `resolver` | `HostResolver` | `SshConfigResolver` (`src/ssh/resolver.rs`) | -| `metadata` | `MetadataStore` | `MetadataDb` (`src/metadata/db.rs`) | -| `store` | — | `LauncherStore` (`src/store/mod.rs`) | -| `launcher` | `TerminalLauncher` | kitty/ghostty/custom ([integrations](../integrations/external-terminals.md)) | -| `password_store` | `PasswordStore` | `OsKeyring` ([secrets](../security/secrets.md)) | +| `resolver` | `Box` | `SshConfigResolver` (`src/ssh/resolver.rs`) | +| `metadata` | `Arc` | `MetadataDb` (`src/metadata/db.rs`) | +| `store` | `Arc` | SQLite `launcher.db` (`src/store/`) | +| `password_store` | `Box` | `OsKeyring` behind `NamespacedPasswordStore`, falling back to a credentials file ([secrets](../security/secrets.md)) | + +That is the whole struct — **exactly four fields**. The fifth dep the older +architecture had (`TerminalLauncher`) no longer exists: the external-terminal +subsystem and `src/launcher/` were deleted in 0.10.0, sessions run in the +embedded PTY (`src/session/`), and the CLI `sshub host connect` path spawns +ssh/mosh directly via `std::process::Command` (`src/cli/host.rs cmd_connect`). + +- **Modes**: `AppMode` (`src/app/types.rs`) has **38 variants** — `Normal`, + `Search`, `TagFilter`, `HostDetail`, `HostForm`, `IdentityForm`, + `KeygenForm`, `GroupForm`/`GroupManage`/`GroupFieldPicker`, `TunnelForm`/ + `TunnelHostPicker`/`TunnelReconnectSettings`, `SessionPicker`, + `PushKeyHostPicker`/`PushKeyIdentityPicker`, `FieldPicker`, `KeybindEditor`, + `Settings`, `ThemePicker`, `ConfirmQuit`/`ConfirmDelete`/`ConfirmDiscard`, + `Help`, `Palette`, `ImportPrompt`, `SftpPrompt`, `Connecting`, `Session`, + `BroadcastPickTarget`/`BroadcastCommand`/`BroadcastPreview`, `Notice`, + `KnownHosts`, `LogBrowser`, and the snippet trio + `SnippetManage`/`SnippetForm`/`SnippetPicker`. Every overlay is a mode; key + dispatch lives in `src/app/keys.rs` (`handle_key` matches `self.mode` to a + per-mode handler, with `SessionPicker`/`SnippetPicker`/session modes routed + early so the PTY keeps Ctrl+C), and per-mode handlers live in `src/app/*.rs`. + `is_overlay_mode` treats everything except `Normal | Connecting | Session` as + a popup, which is what the render pipeline's snapshot slides key off. +- **Tabs are not an enum**: `App.active_tab: usize` (0–4 = hosts, sftp, + tunnels, identities, audit). Be careful when adding tabs — there is no type + safety here; `render_tab_body` falls back to the hosts body for out-of-range + indices, and `anim_prev_tab` lets `detect_tab_switch` notice tab changes made + by the many code paths that assign `active_tab` directly. +- First run with no hosts drops straight into `Help` mode + (`App::new_with_profile`). +- `App::new_with_deps` (the test constructor) uses built-in themes only, reads + no config directory, and spawns **no workers** — the OS-detect worker is + started only by the on-disk constructor `new_with_profile`, so + dependency-injecting tests stay offline and never leak an ssh-probing thread. + +### Runtime theme system + +`App` privately owns a `ThemeManager` (`src/theme/manager.rs`); there is no +global mutable theme state. Renderers read `app.theme()`; the only two mutation +paths are `App::activate_resolved_theme` and `App::replace_theme_manager`, and +both run `invalidate_theme_visual_state()`, which drops every buffer snapshot +and in-flight slide captured under the old theme **before** the next frame — +otherwise the background passes (which select cells by colour) and the slide +blits would composite cells from two themes. + +The fallback policy lives in `ThemeManager` alone: an unknown or unresolvable +`appearance.active_theme` degrades non-fatally to the embedded `default` theme, +keeps `saved_id` verbatim, and never rewrites `config.toml`, so repairing the +theme file is enough to get the user's choice back. `load_themes_from` is +deliberately infallible — a broken `themes/` directory yields built-ins plus a +one-line notice, never a failed startup. + +The **ThemePicker** overlay (`AppMode::ThemePicker`, opened from Settings) +previews a theme on the whole UI on every navigation step +(`activate_resolved_theme`) but only `Enter` persists — activate, write +`config.toml` through the profile-aware writer, then `mark_saved()`, which +adopts the derived `active_id`. `Esc` restores the original (by id, falling +back to the `Rc` captured at open), and `r` reloads the themes directory, +replacing the registry and keeping a tombstone for rows that vanished. + +Two transparency switches compose with this: `App::theme()` returns the +active theme **with its ground released** when +`appearance.transparent_sshub_background` is set, while +`App::base_theme()` (as authored) is what the remote PTY grid reads, so the +SSHub-surfaces switch and the session-grid switch stay independent. -- **Modes**: `AppMode` (`src/app/types.rs`) has ~26 variants — `Normal`, `Search`, `TagFilter`, `HostDetail`, `HostForm`, `IdentityForm`, `GroupForm`/`GroupManage`, `TunnelForm`, `Palette`, `Settings`, `KeybindEditor`, `Help`, `ConfirmQuit`/`ConfirmDelete`/`ConfirmDiscard`, `Connecting`, `Session`, etc. Overlays are modes; key dispatch lives in `src/app/keys.rs` and per-mode handlers in `src/app/*.rs`. -- **Tabs are not an enum**: `App.active_tab: usize` (0–4 = hosts, sftp, tunnels, identities, audit). Be careful when adding tabs — there is no type safety here. -- First run with no hosts drops straight into `Help` mode. -- The `TerminalLauncher` dependency is retained but dead at runtime: sessions run in the embedded PTY (src/session/), and the CLI `sshub host connect` path spawns ssh/mosh directly via std::process::Command (src/cli/host.rs cmd_connect), bypassing TerminalLauncher entirely. The trait is exercised only by its own module unit tests and test doubles; App.launcher itself is never called from any production code path. +The old fixed palette survives only under `#[cfg(test)]` (`src/tui/theme.rs` +`legacy`) as the witness for `default` parity proofs; productive code can only +obtain colours from `ResolvedTheme`. What stayed in `src/tui/theme.rs` is the +string→status-role mapping and the sparkline glyph ramp — logic, not colour. ## Render pipeline (`src/tui/`) -`tui::render` (`src/tui/mod.rs`): +`tui::render` (`src/tui/mod.rs`) resolves a `FrameComposition` **once per frame +from a single clock reading** — the exit/enter slide offsets, the session-tab +slide progress, and every region that carries remote output (`protected`). +Those passes must agree about where a transition is, because the slide's blit +and the protection of what it blitted are one animation frame or they are a +bug. Then: -1. Optional opaque-background backfill (Settings toggle for transparent terminals). -2. `Connecting | Session` modes render the fullscreen session view ([sessions](../workflows/sessions-sftp.md)). -3. Otherwise the bento-grid dashboard chrome from `src/tui/dashboard_layout.rs`: header, tab bar, three-column body, footer. Tab body is dispatched by `active_tab`. (`src/tui/layout.rs`'s older `root_layout` appears superseded — likely dead code.) -4. Overlay popups are dispatched on `app.mode`. `fit_popup` clamps popup rects because `u16::clamp` would otherwise panic and "crash the whole TUI on a terminal smaller than the popup". +1. `render_inner` — the fullscreen session view whenever + `session_is_rendered()` (which also counts the session and snippet pickers + floated over a live session), otherwise the bento-grid dashboard chrome from + `src/tui/dashboard_layout.rs`: header (wordmark, counting stats, session + strip, clock), tab bar, three-column body, footer. Tab bodies are dispatched + by `active_tab`; a zoomed panel (issue #18) takes over the body. +2. Session view extras: enter slide, tab slide, and the picker backdrops. + Otherwise: the dashboard snapshot is refreshed (so entering a session has + something to slide *over*), then the session-exit slide runs. +3. Popup machinery: `capture_popup_snapshot` records the popup drawn this frame + (popups always draw at rest via `popup_open_rect`, which sets + `app.last_popup_rect`); `render_popup_open` drops a fresh popup in from off + the top of the screen (restoring the dashboard backdrop behind it) and + `render_popup_close` throws a just-closed popup's snapshot upward — both + over `POPUP_ANIM` (260 ms). Overlay→overlay transitions (e.g. a form + bouncing to its discard-confirm on held Esc) replay neither. +4. `apply_app_background` — the three-pass background handling (below). +5. `apply_panel_selection` (reversed cells + text extraction for the zoomed + panel) and the splash fade that lets the dashboard fade up out of the intro + animation. -- **Screens** (`src/tui/screens/`): hosts, sftp, tunnels, keys (identities), audit, help, palette, settings, keybind_editor, host_form, group_form, group_manage, field_picker, session_host_picker, tag_filter, tunnel_reconnect, keychain. -- **Widgets** (`src/tui/widgets/`): header, footer, tab_bar, status_bar, host_list, hosts_panel, detail_panel, middle_stack (host card / agent / latency + SSH log panel), right_stack (recent hosts, auth sparkline, ping), panel_box. -- **Theme** (`src/tui/theme.rs`): fixed hex palette (BG `#0b0d10`, green accent `#9ec99b`) with semantic style helpers and a sparkline ramp. Startup animation: `src/tui/animation.rs` (33 ms loop, toggleable in Settings). +Overlay popups are dispatched on `app.mode` in a large match, each rendering +from `src/tui/screens/`. **Screens**: hosts body via widgets, sftp, tunnels, +keys, audit, help, palette, settings, keybind_editor, host_form, identity/keygen +forms, group_form, group_manage, field_picker, session_picker, push_key_pickers, +tag_filter, tunnel_reconnect, keychain, known_hosts, log_browser, snippet_form, +snippet_manage, snippet_picker, theme_picker, broadcast. **Widgets** +(`src/tui/widgets/`): header, footer, tab_bar, hosts_panel, host_list, +detail_panel, middle_stack (host card / agent / latency + SSH log panel), +right_stack (recent hosts, auth sparkline, ping), panel_box. `src/tui/layout.rs`'s +older `root_layout` is not referenced by the pipeline — only its own unit tests +use it. + +`fit_popup` clamps popup rects with `desired.clamp(min.min(avail), avail)` +because a plain `u16::clamp` would assert `min <= max` and crash the whole TUI +on a terminal smaller than a popup's minimum. + +### Three-pass background handling + +SSHub is **opaque out of the box**; transparency is an explicit per-surface +choice (`appearance.transparent_sshub_background` for SSHub's own surfaces, +`appearance.transparent_session_background` for the remote grid). After all +widgets have drawn, `apply_app_background` fills the still-`Color::Reset` cells +in three deliberately separate passes: + +1. **Theme app background** — a theme that resolved + `components.app.background` to a real colour or gradient paints SSHub's own + surfaces, excluding the protected PTY regions. +2. **PTY ground** (`apply_pty_ground`) — owns exactly those protected regions + (the resting viewport plus the travelling bands of an exit or tab slide), backing + the remote grid with the theme's `semantic.pty_background`/`pty_foreground` + pair. Both channels are filled (foreground too), so reverse video and + light-theme grounds cannot leak the emulator's own colour; each channel + falls back on its own (canvas for the ground, plain text for the foreground) + when a theme left it to the emulator. Skipped entirely under + `transparent_session_background`. +3. **Canvas** — whatever is left is filled with `semantic.canvas`, for themes + that resolved their ground to `"terminal"`. Skipped under + `transparent_sshub_background`. + +Every pass selects on `Color::Reset`, so none can touch a cell a widget already +coloured, and the exclusion of the protected regions is load-bearing: with +`transparent_session_background` on, pass 2 writes nothing and the canvas must +not land on the grid the user asked to see through. + +### Animation: tween, blit, and reduced motion + +- `src/tui/tween.rs` — dependency-free primitives: `SlideAnim` (eased `Rect` + interpolation, `Easing::Out`/`InOut`), `progress`, `color_lerp` (RGB-only, + non-RGB switches at the halfway mark), breathing `pulse_now`, and braille + spinners advanced by the wall clock. +- `src/tui/blit.rs` — the cell-level effects behind the slides: `blit` copies a + standalone buffer's cells at an eased offset (going through cells instead of + shifting a `Rect` lets a layer travel past the screen edge), `snapshot` + clones cells with absolute coordinates, and `fade` blends a region's content + toward its per-cell theme ground (`fade_ground` substitutes + `semantic.canvas` where the role resolved to `Color::Reset`), honouring + exclusions. +- Named durations gate each effect: `SPLASH_FADE` 360 ms, `CONTENT_FADE` 140 + ms, `SFTP_NAV_ANIM`/`SFTP_QUEUE_ANIM` 200 ms, `PING_FLASH` 420 ms, + `FOLD_ANIM` 180 ms, `SELECT_ANIM` 120 ms, `TAB_ANIM` 220 ms, `POPUP_ANIM` + 260 ms, `SESSION_ANIM` 280 ms, `SFTP_ANIM` 260 ms. +- **Session slides**: entering a session slides the freshly rendered view in + from the right over `dashboard_snapshot` (skipped entirely when no + same-area snapshot exists — blitting a stale-theme or wrong-size buffer is + worse than a cut); leaving slides the captured `session_snapshot` off to the + right over the dashboard already drawn beneath; session-tab switches travel + only the PTY body, keeping the header strip fixed as the reference. +- **Reduced motion**: `appearance.disable_animation` (`App::motion_enabled()`) + is the single gate — also flipped in Settings. Animation call sites jump + straight to the final state, the intro splash is skipped, and `animating()` + returns false so the loop never bumps to 60 fps for nothing. +- The startup intro itself (`src/tui/animation.rs`, ~10 s timeline, 33 ms poll + loop in `run_animation`) exits on Enter/Space/Esc/q, is skipped under + auto-quit or `disable_animation`, and honours the last-used profile's motion + preference even before the picker runs. + +## Background workers + +Every blocking operation lives on its own thread; the frame loop only drains. + +| Worker | Module | Shape | Drained by | +|---|---|---|---| +| Config file watcher | `src/watcher.rs` | `notify` recommended watcher + 300 ms debounce thread | `watcher_rx` → `reload_hosts()` | +| Ping worker | `src/ping.rs` | one thread, sequential `ping -c 1 -W 1` pass every 30 s | `ping_rx` → 30-sample ring per host | +| SFTP worker (right pane) | `src/sftp/worker.rs` | one thread per connection; owns the blocking ssh2 transport; command loop | `sftp_rx` → `apply_sftp_event` | +| SFTP worker (left pane) | `src/sftp/worker.rs` | second instance only while the left pane browses another server | `sftp_rx2` → `apply_sftp_event_left` | +| OS-detect worker | `src/osinfo/detect.rs` | single thread blocking on `OsDetectCmd`, self-terminating when the sender drops | `os_detect_rx` → `apply_os_detect` | +| Broadcast pool | `src/broadcast/mod.rs` | `min(concurrency, tasks)` threads (default `DEFAULT_CONCURRENCY = 8`) over a shared preloaded task queue | `BroadcastState.rx` via `tick_broadcast` | +| PTY reader / stderr siphon / writer | `src/session/pty.rs` | three threads per embedded session | `PtyEvent` channel via `Session::drain` | + +Tunnels are the exception that proves the shape: `TunnelManager` owns child +processes but is ticked synchronously each frame (`check_health` uses +`try_wait`); it is not a thread pool. See +[tunnels](../workflows/tunnels.md) for the reconnect backoff lifecycle. + +Worker lifecycle invariants: + +- **Self-termination by channel drop.** The ping, SFTP, and OS-detect workers + exit when the UI drops their command/event `Sender`/`Receiver` — a send + failure or a closed command channel ends the thread. Restarting the ping + worker on `reload_hosts()` drops the old receiver, winding the previous + thread down instead of leaving it pinging deleted addresses. +- **The SFTP worker connects once** and then services commands + (`ListDir`, `RunQueue`, `Remove`, `Mkdir`, `Rename`, `Chmod`, `Cancel`) until + the channel closes; a `ConnectFailed` event ends it. Progress events are + throttled to one per 64 KiB. +- **The OS-detect probe is silent on failure** — an unrecognised or failed + probe leaves the host's `os_icon` empty and retries on the next connect; the + probe is only kicked off for managed hosts whose `os_icon` is still empty + (an in-flight set, `os_detect_inflight`, prevents duplicate probes for one + host). +- **The broadcast pool guarantees exactly one terminal event per task** + (`Finished` or `Failed`), including under cancel: un-started tasks emit + `Failed{reason:"cancelled"}` and in-flight children are killed. Workers pull + from a preloaded, sender-dropped channel under a shared `Mutex`, so `recv` + never blocks; the real `SshCommandRunner` shells ssh non-interactively + (BatchMode splice, `ConnectTimeout=8`, `StrictHostKeyChecking=accept-new`) + with a 60 s run cap and an `AtomicBool` cancel checked on a 50 ms `try_wait` + poll. +- **The PTY writer is the bounded-queue fix (0.15.1).** `write_all` on the PTY + master blocks once the child stops reading its stdin — a remote stuck in a + terminal-query loop parked every tab, input and rendering included, measured, + not theorised. All writes toward the child (keystrokes, the auto-typed + secret, pastes, the emulator's query answers) now go through one writer + thread behind a `sync_channel` bounded at `WRITE_QUEUE_LEN = 64`, preserving + order while taking the blocking write off the frame loop. `PtyRuntime::write` + uses `try_send`, so a full queue is an immediate "the remote is not reading" + error — human typing never queues that deep, so a full queue means the far + end is gone. `Drop` closes the channel but **deliberately does not join** the + writer: a write blocked on a dead child only returns once the slave closes, + and joining would move the freeze into session teardown. +- **The stderr FIFO keeps handshake noise off the grid.** The child's stderr is + routed through a `mkfifo` side channel (0600, opened `O_RDWR | O_NONBLOCK` so + an empty FIFO yields `EAGAIN` rather than a premature EOF) via an + `sh -c 'exec "$@" 2>"$SSHUB_STDERR_FIFO"'` wrapper — keeping the ssh PID for + signal delivery — so `ssh -v` debug output lands in the session's + `debug_log` (connect spinner, host-key fingerprint scan) instead of the + render grid. If the FIFO cannot be set up the connect degrades to the plain + PTY rather than failing. ## File watcher (`src/watcher.rs`) -A `notify` watcher monitors the ssh config's **parent directory**, not the file — editor rename-saves swap inodes and silently detach file-level watches. Events are debounced (300 ms) on a thread and delivered as `WatchEvent::ConfigChanged`, which triggers `app.reload_hosts()` in the event loop. This is the "hot reload" feature. +A `notify` recommended watcher monitors the ssh config's **parent directory**, +not the file — editors save by writing a temp file and renaming it over the +config, which swaps the inode and silently detaches a file-level watch. Events +are filtered down to the config file's name (`Modify | Create | Remove`) and +debounced with a **fixed 300 ms window**: the first config-touching event opens +the window, everything arriving before it expires is swallowed, and exactly one +`WatchEvent::ConfigChanged` is emitted. The loop collapses the burst into one +`app.reload_hosts()` — the hot-reload feature. + +The debounce loop is abstracted behind an `EventSource` trait (recv / +recv_timeout / clock) so tests inject a scripted event list with a virtual +clock instead of sleeping on real time. A watcher that cannot start (missing +config, no watch handle) disables hot reload with a warning; it never blocks +startup. ## Where to go next - [Data model & storage](data-model.md) — what the event loop loads and persists. - [TUI dashboard workflow](../workflows/tui.md) — user-facing tabs, keys, and screens built on this state machine. -- [Testing strategy](../testing/strategy.md) — how `AppDeps` doubles and `TestBackend` make this loop testable. +- [Sessions & SFTP](../workflows/sessions-sftp.md) — the PTY/session and transfer workers this loop drains. +- [Testing strategy](../testing/strategy.md) — how `AppDeps` doubles, the offline `new_with_deps` constructor, and `TestBackend` make this loop testable. diff --git a/openwiki/changelog-coverage.md b/openwiki/changelog-coverage.md index d8d22590..929608b1 100644 --- a/openwiki/changelog-coverage.md +++ b/openwiki/changelog-coverage.md @@ -1,53 +1,210 @@ --- type: Audit Notebook title: Changelog to OpenWiki Coverage -description: Ledger of every CHANGELOG.md feature, fix, and behavior change reviewed against source evidence and mapped to OpenWiki pages. +description: Ledger of every CHANGELOG.md feature, fix, and behavior change from 0.1.0 through 0.16.0 (plus Unreleased) reviewed against source evidence and mapped to the OpenWiki page that documents it, with Covered/Pending status and run metadata. resource: CHANGELOG.md tags: [changelog, coverage, audit, openwiki] +verified: + - by: openwiki/0.5.1 + at: 2026-09-11T12:46:43.817Z +sources: + - id: openwiki-source-651d1fb6c9e49916a916ab51 + resource: repo://Cargo.toml + - id: openwiki-source-ca6cb4b1a14fd7969dfae3ec + resource: repo://CHANGELOG.md + - id: openwiki-source-06170d7ecd38e316d14bb54a + resource: repo://docs/host-sync-design.md + - id: openwiki-source-29f2ffe501d0bdc557fcc14a + resource: repo://npm/wrapper/bin/sshub.js + - id: openwiki-source-e8c0db94ed2adcf662c346a3 + resource: repo://src/app/keys.rs + - id: openwiki-source-c0d718e8edd8adec04ab3210 + resource: repo://src/app/push_key.rs + - id: openwiki-source-7da0877d78263557d03ac935 + resource: repo://src/app/types.rs + - id: openwiki-source-f531855e3a279281c944b8ad + resource: repo://src/app/util.rs + - id: openwiki-source-396ca803e3cf512685ab87bc + resource: repo://src/cli/exec.rs + - id: openwiki-source-2a737474d86fc75cc9d9694f + resource: repo://src/config.rs + - id: openwiki-source-38dc2802701bc3b7c43855d1 + resource: repo://src/keybinds.rs + - id: openwiki-source-b9079954c10a783a989073f6 + resource: repo://src/known_hosts.rs + - id: openwiki-source-f2fe236a8856917d43dfeb9f + resource: repo://src/log_browser.rs + - id: openwiki-source-b55a21a31ede1b56cd31a6a6 + resource: repo://src/main.rs + - id: openwiki-source-39c7504511d4abe5494f1fca + resource: repo://src/session/askpass.rs + - id: openwiki-source-251a566921053d647fd490f2 + resource: repo://src/session/keys.rs + - id: openwiki-source-bf6cda76234f60dd7eecaa7f + resource: repo://src/session/mod.rs + - id: openwiki-source-6ea4bd654665d0a13bbccee4 + resource: repo://src/session/pty.rs + - id: openwiki-source-8c90e650e97e8c2db8feaa52 + resource: repo://src/ssh/host.rs + - id: openwiki-source-661a6d64d056120219351adf + resource: repo://src/ssh/resolver.rs + - id: openwiki-source-ef7bbf76631c2cc5e3fe2e35 + resource: repo://src/store/hosts.rs + - id: openwiki-source-b829ac603b13b9d0bcc902a4 + resource: repo://src/store/migrate.rs + - id: openwiki-source-14a9e64a66b1cefa254fa04b + resource: repo://src/text_input.rs + - id: openwiki-source-2d5a061fae2d850d0f051605 + resource: repo://src/theme/catalog.rs + - id: openwiki-source-082d0e6abbb7a34f426ec1bf + resource: repo://src/theme/role_matrix.snapshot + - id: openwiki-source-b3d2abf8ff74eda5980303d0 + resource: repo://src/tunnel/spawn.rs +generated: { by: "openwiki/0.5.1", at: "2026-09-11T12:46:43.817Z" } --- # Changelog to OpenWiki Coverage -This notebook audits every entry in `CHANGELOG.md` against the source tree and links each reviewed item to exact wiki pages. `Covered` means the behavior is documented and cross-linked; `Pending` means an item still needs a dedicated or more precise treatment. +This notebook audits every entry in `CHANGELOG.md` against the source tree and links each reviewed item to exact wiki pages. `Covered` means the behavior is documented and cross-linked; `Pending` means an item still needs dedicated or more precise treatment. `Unreleased` is currently empty in `CHANGELOG.md`, so there is nothing to map from it this run. ## Coverage Ledger +### 0.16.0 + +| Release / entry | Wiki page(s) | Status | Notes | +| --- | --- | --- | --- | +| 0.16.0: Session log browser (`Shift+L`) | [Sessions & SFTP](workflows/sessions-sftp.md), [Data model](architecture/data-model.md) | Covered | Hosts → rotated segments → viewer with escape-code stripping, `/` search (`n`/`N`), `b`/`m` bookmarks, `VIEWER_READ_CAP` (4 MiB) bounded reads, and the `log_bookmarks` table (schema v15, keyed by on-disk location) all audited in `src/log_browser.rs`, `src/app/log_browser.rs`, and `src/store/migrate.rs`. | +| 0.16.0: Version badge shows install channel | [TUI dashboard](workflows/tui.md), [Integrations](integrations/external-terminals.md) | Covered | `npm`/`cargo`/`source` resolution (env marker first, then path heuristics), byte-identical npm binary rationale, `SSHUB_VERSION_LABEL` verbatim override, and hidden-label demo path checked in `src/tui/widgets/tab_bar.rs`. | + +| 0.16.0: Command snippets library | [Command snippets](workflows/snippets.md), [Data model](architecture/data-model.md) | Covered | `Shift+S` manager, in-session `Ctrl+N` fuzzy picker (Enter runs, Tab inserts without newline), rebindable `SnippetsManage`/`SessionSnippets` actions, and the `snippets` table (v13→v14) audited in `src/app/snippets.rs`, `src/keybinds.rs`, and `src/store/snippets.rs`. | +| 0.16.0: Group-delete confirmation draws | [TUI dashboard](workflows/tui.md) | Covered | Notice set after `enter_group_manage()` re-entry and `render_group_manage_popup` drawing it on its own row verified in `src/app/keys.rs` and `src/tui/screens/group_manage.rs` (with its render test). | +| 0.16.0: SFTP regains username/key auth for ssh_config-imported hosts (#120) | [Sessions & SFTP](workflows/sessions-sftp.md), [Headless CLI](workflows/cli.md) | Covered | `sftp_ssh_host` resolves the alias live through the same `ssh -G` machinery the import used, for both the TUI browser and headless `sshub sftp`, letting the resolved config fill username/identity/certificate while SSHub-managed values win — audited in `src/app/util.rs`, `src/app/sftp.rs`, and `src/cli/sftp.rs`. | + +### 0.15.2 + +| Release / entry | Wiki page(s) | Status | Notes | +| --- | --- | --- | --- | +| 0.15.2: Ctrl+U / Ctrl+W form chords | [TUI dashboard](workflows/tui.md) | Covered | `clear_before_cursor`/`delete_word_before` in `src/text_input.rs`, host/identity/tunnel form wiring, fixed-not-rebindable status, and multibyte/clamp behavior audited. | +| 0.15.2: Masked-password reveal hint + popup sizing | [TUI dashboard](workflows/tui.md), [Secrets](security/secrets.md) | Covered | Reveal bind riding the focused password row (`secret_field_hints`), popup sized to drawn rows, and the 80x24 render test verified in `src/app/tests/host_form.rs` and the form screens. | +| 0.15.2: Alternate-screen wheel and PageUp/PageDown to the remote app | [Embedded terminal surface](workflows/session-terminal.md) | Covered | `alternate_scroll_keys` (xterm `alternateScroll`, three arrows per notch), Shift opt-out, the tmux `set -g mouse on` toast (`hint_alternate_scroll`), the `-X` pager caveat, and shared guarded scroll path audited in `src/session/keys.rs` and `src/app/session.rs`. | +| 0.15.2: Wheel says where tmux's switch is | [Embedded terminal surface](workflows/session-terminal.md) | Covered | Once-per-session toast fired on the notch, not on process guessing, checked in `src/session/mod.rs`. | +| 0.15.2: Wheel cannot drag the selection | [Embedded terminal surface](workflows/session-terminal.md) | Covered | `scroll_with_selection` shifts by however far the view actually moved (alternate screen = nothing) audited in `src/session/mod.rs`. | +| 0.15.2: Drag at the prompt selects text (mouse-mode guard) | [Embedded terminal surface](workflows/session-terminal.md), [Secrets](security/secrets.md) | Covered | Forwarding requires mouse-report AND alternate screen; Shift inverts either way; chrome rows excluded from reporting; selector normalized to `c` in `src/osc52.rs` — all verified in `src/session/keys.rs` (`selects_locally`) and `src/app/session.rs`. | +| 0.15.2: Tunnel list resync across windows | [Tunnels](workflows/tunnels.md) | Covered | `sync_tunnels_from_store` re-reads the store every 2 s, re-anchors selection by id, leaves running children alone (delete+insert, no `UPDATE`), and keeps run state per-window — audited in `src/app/tunnels.rs`. | +| 0.15.2: Ctrl+U/End/Backspace no longer edit the address from non-text rows | [TUI dashboard](workflows/tui.md) | Covered | `active_field_mut` returning `Option` for host/identity/tunnel forms verified in `src/app/types.rs` and pinned by the host-form regression test. | +| 0.15.2: `sshub exec` documents the quoted `&&` form | [Headless CLI](workflows/cli.md) | Covered | `-- 'ls && uptime'` quoted-form note and `--tty` for full-screen commands in `src/cli/help.rs` (behavior unchanged) verified. | + +### 0.15.1 + +| Release / entry | Wiki page(s) | Status | Notes | +| --- | --- | --- | --- | +| 0.15.1: Terminal answers cursor-position and status queries | [Embedded terminal surface](workflows/session-terminal.md) | Covered | DSR 6 (1-based, clamped) from the grid, DSR 5, and DA1 (`?1;2c`) answered; kitty protocol and secondary DA deliberately silent; rate-limited via the `REPLY_BURST_BYTES`/`REPLY_BYTES_PER_SEC` token bucket verified in `src/session/parser.rs` and `src/session/mod.rs`. | +| 0.15.1: PTY writes move to a dedicated thread behind a bounded queue | [Embedded terminal surface](workflows/session-terminal.md), [Sessions & SFTP](workflows/sessions-sftp.md) | Covered | `WRITE_QUEUE_LEN` (64), ordering guarantee for keystrokes/auto-typed secret/pastes/query answers, and the measured freeze-elimination rationale audited in `src/session/pty.rs`. | + +### 0.15.0 + +| Release / entry | Wiki page(s) | Status | Notes | +| --- | --- | --- | --- | +| 0.15.0: `vendored` feature (link system OpenSSL) | [Build & Release](operations/build-release.md), [Quickstart](quickstart.md) | Covered | `default = ["vendored"]`, the dev-only `--no-default-features` opt-out (`just test` uses it), and the never-ship-without-it rule verified in `Cargo.toml` and the Justfile. | +| 0.15.0: `sshub exec` | [Headless CLI](workflows/cli.md) | Covered | BatchMode with no secret, `--tty`, `--timeout` (exit 124, own process group, SIGKILL of the group), `--format json` record, audit `via exec` without the command, transcript skip, mosh refusal — verified in `src/cli/exec.rs`. | +| 0.15.0: Shift+P fixes (identities reload + silent bail-out notices) | [Hosts, Groups & Identities](domain/hosts-identities.md) | Covered | `trigger_push_key_from_hosts` reloads identities before reading the lazy cache and both bail-outs now message via `host_notice` — audited in `src/app/push_key.rs`. | +| 0.15.0: Dashboard notices render unzoomed | [TUI dashboard](workflows/tui.md) | Covered | `host_notice` toast gating on `panel_zoomed` removed; the two silent push-key bail-outs verified. | + +### 0.14.2 + +| Release / entry | Wiki page(s) | Status | Notes | +| --- | --- | --- | --- | + +| 0.14.2: Theme persistence through the profile writer | [Runtime theme system](workflows/theming.md), [Data model](architecture/data-model.md) | Covered | `commit_theme_picker` writes through `App::config_target()` exactly once, activate → write → `mark_saved`, and the real-`Enter` regression test verified in `src/app/theme_picker.rs` and `src/app/keys.rs`. | +| 0.14.2: Leading-dash host guard (option injection) | [Secrets](security/secrets.md), [Hosts, Groups & Identities](domain/hosts-identities.md) | Covered | `safe_ssh_target` rewrites to the `ssh://` URI form for connect/tunnels/alias connects; `reject_option_like`/`is_option_like` refuse leading-dash name/address/username at write time; import drops only the poisoned entry and counts refusals — verified in `src/ssh/host.rs` and `src/store/hosts.rs`. | + +### 0.14.0 + +| Release / entry | Wiki page(s) | Status | Notes | +| --- | --- | --- | --- | + +| 0.14.0: Runtime theme system (TOML themes, picker, built-ins, gradients, PTY ground, `sshub theme` CLI) | [Runtime theme system](workflows/theming.md), [Headless CLI](workflows/cli.md), [TUI dashboard](workflows/tui.md) | Covered | Three-layer file format with inheritance, five built-ins, whole-UI preview with rollback, 234-role catalogue (`src/theme/role_matrix.snapshot`), static gradients in five directions, `pty_background`/`pty_foreground` pair semantics, and the headless dispatched-before-bootstrap CLI verified in `src/theme/`, `src/app/theme_picker.rs`, and `src/main.rs`. | +| 0.14.0: Isolated profiles (issue #17) | [Data model](architecture/data-model.md), [Quickstart](quickstart.md) | Covered | Profile-owned databases/settings/credentials/logs/tunnel state, per-profile SSH config source, startup picker, `--profile`, `--manage-profiles`, and legacy migration documented. | + +| 0.14.0: Transparency choice; `opaque_background` gone | [Runtime theme system](workflows/theming.md), [TUI dashboard](workflows/tui.md) | Covered | Two independent `Ctrl+H` toggles (`transparent_sshub_background`, `transparent_session_background`), ground release keeping chrome, retired-key removal via `drop_retired_keys` verified in `src/config.rs` and `src/app/mod.rs`. | +| 0.14.0: Known hosts manager follow-ups (#89) | [Known hosts manager](workflows/known-hosts.md) | Covered | Per-line fingerprinting via `ssh-keygen -l -f -`, surfaced load/refresh errors, symlinked-file refusal, negated-pattern (`!host`) delete refusal, and remappable Ctrl+D/Ctrl+R actions verified in `src/known_hosts.rs`. | +| 0.14.0: Quoted `Host` aliases unquoted | [Hosts, Groups & Identities](domain/hosts-identities.md), [Testing strategy](testing/strategy.md) | Covered | OpenSSH-style quote stripping in `listable_host_alias` and the differential test `listed_aliases_round_trip_through_real_ssh` (per `docs/oracle-tests.md`) verified in `src/ssh/resolver.rs`. | + +### 0.13.0 + +| Release / entry | Wiki page(s) | Status | Notes | +| --- | --- | --- | --- | +| 0.13.0: Known hosts manager and connect fingerprint | [Known hosts manager](workflows/known-hosts.md), [Secrets](security/secrets.md) | Covered | Manager overlay, guarded `ssh-keygen -R` deletion (hashed/marker/wildcard/wildcard-shadowing refusals), and first-wins fingerprint cache documented. | +| 0.13.0: Help/H key migration | [TUI dashboard](workflows/tui.md) | Covered | Help bound to `?` only, `H` for known hosts, one-time migration of legacy `help` settings. | +| 0.13.0: OSC 52 PTY relay | [Embedded terminal surface](workflows/session-terminal.md), [Secrets](security/secrets.md) | Covered | Visibility boundary, 64 KiB cap + 8-entry queue with named drop counters, empty-write ignored (no remote clear), read refusal, `[clipboard].relay_from_pty` switch. | +| 0.13.0: Terminal-stream demo recording | [Integrations](integrations/external-terminals.md) | Covered | `demo/record.py`, asciicast + agg at 1.00x timing checks, fixture home documented. | +| 0.13.0: SFTP local path display | [Sessions & SFTP](workflows/sessions-sftp.md) | Covered | Local `$HOME` collapse to `~` (`contract_home`), remote paths untouched. | + +### 0.11.0 + +| Release / entry | Wiki page(s) | Status | Notes | +| --- | --- | --- | --- | +| 0.11.0: Session switcher/local shell/shared picker | [Sessions & SFTP](workflows/sessions-sftp.md), [TUI dashboard](workflows/tui.md) | Covered | `Alt+S` fuzzy switcher with lifecycle dots, `Ctrl+Shift+T` local shell, shared three-purpose picker widget. | +| 0.11.0: Searchable Help/keybinding editor | [TUI dashboard](workflows/tui.md) | Covered | Type-to-filter, filtered-row rebinding, Ctrl+A/R/X row actions documented. | +| 0.11.0: Secret reveal/copy/delete and keyring fallback | [Secrets](security/secrets.md) | Covered | Masked prefill, `Ctrl+R` reveal+copy / `Ctrl+Y` copy-only, cleared-field deletion, `credentials.json` fallback with migration-back-on-keyring. | +| 0.11.0: Ad-hoc connect | [TUI dashboard](workflows/tui.md) | Covered | Validated `[user@]host[:port]` row (IPv6 brackets) passed after `--`. | +| 0.11.0: Public-key push and key generation | [Hosts, Groups & Identities](domain/hosts-identities.md), [Secrets](security/secrets.md) | Covered | `Shift+P` push (umask 077, idempotent exact-line append, `ssh-keygen -y` extraction, askpass staging, audited result) and `g` key generation (Ed25519/RSA-4096, no overwrite) documented on the dedicated page. | +| 0.11.0: npm installation | [Integrations](integrations/external-terminals.md), [Build & Release](operations/build-release.md), [CI & automation](operations/ci-cd.md) | Covered | npm shim (`SSHUB_INSTALL_CHANNEL=npm`, exec-bit repair, stdio inherit), `sshub-tui` platform optional dependencies, and OIDC-trusted npm publish documented. | +| 0.11.0: UI motion pass | [TUI dashboard](workflows/tui.md), [Runtime architecture](architecture/overview.md) | Covered | Motion summary (popup drop/close, tab slides, zoom morph, host-dot flash, SFTP progress sweep) and `appearance.disable_animation` gating documented; tween/blit pipeline covered on the architecture page. | +| 0.11.0: SFTP two-server/queue/dotfiles/`..` and failure behavior | [Sessions & SFTP](workflows/sessions-sftp.md) | Covered | Two-server relay, queue-while-running, dotfile hiding, parent row, retry/failure semantics documented. | +| 0.11.0: Release profile and askpass upgrade fix | [Build & Release](operations/build-release.md), [Secrets](security/secrets.md) | Covered | Release-profile tradeoffs (lto/codegen-units/strip, empty `RUST_BACKTRACE`) and helper re-resolution on upgrade documented. | +| 0.11.0: Remaining regression fixes (session-strip binds, agent-panel position, stored-secret removal, tab-cycling stays on dashboard, re-enter slide, black-screen slide, narrow footers, DECCKM arrows, SFTP parent/stage/retry/unreachable) | [TUI dashboard](workflows/tui.md), [Embedded terminal surface](workflows/session-terminal.md), [Sessions & SFTP](workflows/sessions-sftp.md) | Covered | Global session-strip handling before per-tab dispatch, DECCKM-following arrow encoding, SFTP failure semantics, and footer pinning each have a documented home; the DECCKM encoder fix lives on the terminal-surface page. | + +### 0.10.0 + | Release / entry | Wiki page(s) | Status | Notes | | --- | --- | --- | --- | -| 0.13.0: Known hosts manager and connect fingerprint | [Known hosts manager](workflows/known-hosts.md), [Secrets](security/secrets.md) | Covered | `src/known_hosts.rs`, `src/tui/screens/known_hosts.rs`, session fingerprint path checked. | -| 0.13.0: Help/H key migration | [TUI dashboard](workflows/tui.md) | Covered | `src/keybinds.rs`, `src/config.rs` checked. | -| 0.13.0: OSC 52 PTY relay | [Sessions & SFTP](workflows/sessions-sftp.md), [Secrets](security/secrets.md) | Covered | Visibility boundary, bounds, config, and read refusal documented. | -| 0.13.0: Terminal-stream demo recording | [Integrations](integrations/external-terminals.md) | Covered | `demo/record.py` and timing-based asciicast pipeline checked. | -| 0.13.0: SFTP local path display | [Sessions & SFTP](workflows/sessions-sftp.md) | Covered | Local `$HOME` collapse and untouched remote paths documented. | -| 0.11.0: Session switcher/local shell/shared picker | [Sessions & SFTP](workflows/sessions-sftp.md), [TUI dashboard](workflows/tui.md) | Covered | `src/app/session_picker.rs`, session dispatch, and picker screens checked. | -| 0.11.0: Searchable Help/keybinding editor | [TUI dashboard](workflows/tui.md) | Covered | Filter and filtered-row rebinding behavior documented. | -| 0.11.0: Secret reveal/copy/delete and keyring fallback | [Secrets](security/secrets.md) | Covered | Credential storage, fallback migration, masking, and copy controls checked. | -| 0.11.0: Ad-hoc connect | [TUI dashboard](workflows/tui.md) | Covered | Validation and `--` argument boundary checked. | -| 0.11.0: Public-key push and key generation | [Hosts, Groups & Identities](domain/hosts-identities.md), [Secrets](security/secrets.md) | Pending | Source behavior audited; dedicated user workflow remains to be expanded. | -| 0.11.0: npm installation | [Build & Release](operations/build-release.md), [CI & automation](operations/ci-cd.md) | Pending | Packaging source audited; npm distribution details need a focused section. | -| 0.11.0: UI motion and panel/layout fixes | [TUI dashboard](workflows/tui.md) | Pending | Source and tests audited; motion-specific behavior is not fully represented. | -| 0.11.0: SFTP two-server/queue/dotfiles/parent and failure behavior | [Sessions & SFTP](workflows/sessions-sftp.md) | Covered | `src/app/sftp.rs`, model, worker, and tests checked. | -| 0.11.0: release profile and askpass upgrade fix | [Build & Release](operations/build-release.md), [Secrets](security/secrets.md) | Covered | Release profile and helper re-resolution checked. | -| 0.11.0: remaining session-strip, agent-panel, cursor-key, and SFTP fixes | [TUI dashboard](workflows/tui.md), [Sessions & SFTP](workflows/sessions-sftp.md), [Hosts, Groups & Identities](domain/hosts-identities.md) | Pending | Regression behavior remains distributed rather than explicitly catalogued. | -| 0.10.0: Broadcast commands | [TUI dashboard](workflows/tui.md) | Covered | The changelog names broadcast behavior, but no standalone `workflows/broadcast.md` concept exists; app/TUI wiring and tests were checked. | -| 0.10.0: panel focus/zoom and PuTTY/mRemoteNG import | [TUI dashboard](workflows/tui.md), [Hosts, Groups & Identities](domain/hosts-identities.md) | Pending | Source audited; detailed workflows remain to be added. | -| 0.10.0: Headless CLI | [Headless CLI](workflows/cli.md) | Covered | Command tree, JSON, exit codes, and guards checked. | -| 0.10.0: external launcher removal | [Integrations](integrations/external-terminals.md) | Covered | Stale launcher documentation corrected. | -| 0.9.0–0.8.0: transport, logging, tunnels, SFTP operations, inputs, version label | [Sessions & SFTP](workflows/sessions-sftp.md), [Tunnels](workflows/tunnels.md), [TUI dashboard](workflows/tui.md), [Secrets](security/secrets.md) | Covered | Existing pages and source were reviewed; no separate page needed. | -| 0.7.0–0.5.x: SFTP, groups, OS detection, selection, clipboard paste, log rendering | [Sessions & SFTP](workflows/sessions-sftp.md), [Hosts, Groups & Identities](domain/hosts-identities.md), [TUI dashboard](workflows/tui.md) | Covered | Existing canonical pages cover the product behavior; minor fixes remain implementation-level. | -| 0.4.0: AGPL relicensing | [Quickstart](quickstart.md) | Pending | License is authoritative in `LICENSE`; quickstart names the current license but lacks release-history detail. | -| 0.3.0–0.1.0: TUI foundation, config/import/hot reload, embedded sessions, initial launcher | [Quickstart](quickstart.md), [Runtime architecture](architecture/overview.md), [Data model](architecture/data-model.md), [Hosts, Groups & Identities](domain/hosts-identities.md), [Testing](testing/strategy.md) | Covered | Source and tests checked against canonical architecture/workflow pages. | +| 0.10.0: Broadcast commands | [Broadcast mode](workflows/broadcast.md) | Covered | Dedicated page now exists: three-stage wizard, bounded worker pool (default 8), docked panel with focus/zoom, audit rows, BatchMode/askpass auth. | +| 0.10.0: Dashboard panel focus + zoom | [TUI dashboard](workflows/tui.md) | Covered | `Alt`+arrows focus, `z`/`Alt+Enter` zoom, zoomed scroll/drag-to-copy, per-panel actions documented. | +| 0.10.0: PuTTY and mRemoteNG import | [Hosts, Groups & Identities](domain/hosts-identities.md) | Covered | Both importers (registry export / `confCons.xml`, SSH-only, mRemoteNG encrypted passwords not decrypted) documented alongside Termius and ssh_config. | +| 0.10.0: Headless CLI | [Headless CLI](workflows/cli.md) | Covered | Command tree, JSON, exit codes, `--yes` guards, completions cache documented. | +| 0.10.0: External launcher removal | [Integrations](integrations/external-terminals.md) | Covered | Removed subsystem status and ignored legacy keys documented; no runtime behavior remains. | + +### 0.9.0 – 0.5.x + +| Release / entry | Wiki page(s) | Status | Notes | +| --- | --- | --- | --- | +| 0.9.0: Mosh transport, session logging, tunnel keep-alive | [Sessions & SFTP](workflows/sessions-sftp.md), [Tunnels](workflows/tunnels.md), [Data model](architecture/data-model.md) | Covered | Schema v12/v13 fields, rotation/retention, backoff config, and audit integration documented. | +| 0.9.0: Session keybind hints, tunnel auth on TUI | [TUI dashboard](workflows/tui.md), [Tunnels](workflows/tunnels.md) | Covered | Config-driven hints and BatchMode/askpass tunnel options documented. | +| 0.8.0: SFTP file ops, recursive transfers, cursor navigation, `SSHUB_VERSION_LABEL` | [Sessions & SFTP](workflows/sessions-sftp.md), [TUI dashboard](workflows/tui.md) | Covered | In-place ops, symlink handling, text-cursor editing, version-label override documented. | +| 0.8.0: SFTP symlink handling, help scroll clamp, settings footer fix | [Sessions & SFTP](workflows/sessions-sftp.md), [TUI dashboard](workflows/tui.md) | Covered | Symlink transfer rules (size from target, dir/broken skip) documented; the two layout fixes are implementation-level and match current screens. | +| 0.7.0: SFTP file transfer tab, OS auto-detection, multi-group/Favorites, settings overlay, richer host card, changed host key prompt, version in tab bar | [Sessions & SFTP](workflows/sessions-sftp.md), [Hosts, Groups & Identities](domain/hosts-identities.md), [TUI dashboard](workflows/tui.md), [Known hosts manager](workflows/known-hosts.md) | Covered | Dual-pane browser, OS-detect worker with silent failures, membership join table with reserved Favorites, and the changed-key prompt documented. | +| 0.7.0: Tab order change, latency panel per-host, ssh log wrap, SFTP picker fixes, keybind migration persistence | [TUI dashboard](workflows/tui.md), [Sessions & SFTP](workflows/sessions-sftp.md) | Covered | Current tab order and latency-panel behavior match; migration-persistence and stale-index fixes are pinned by tests referenced in [testing strategy](testing/strategy.md). | +| 0.5.7 / 0.5.6 / 0.5.0: Selection autoscroll and range preservation, bracketed paste, log clamp/visibility, `j`/`k` in search, mouse text selection | [Embedded terminal surface](workflows/session-terminal.md), [Sessions & SFTP](workflows/sessions-sftp.md), [TUI dashboard](workflows/tui.md) | Covered | Selection autoscroll/edge extension, mouse selection, and bracketed-paste forwarding into the PTY belong to the embedded terminal surface; the `j`/`k`-in-search behavior is on the dashboard page. | + +### 0.1.0 – 0.4.0 + +| Release / entry | Wiki page(s) | Status | Notes | +| --- | --- | --- | --- | +| 0.4.0: AGPL-3.0-or-later relicensing (≤0.3.1 MIT) | [Quickstart](quickstart.md) | Covered | Quickstart states the AGPL-3.0-or-later license; `Cargo.toml` and `LICENSE` remain authoritative. The release-by-release history stays in `CHANGELOG.md` by design — a license note is a live fact, not a changelog replay. | +| 0.3.1: crates.io metadata/docs only | [Build & Release](operations/build-release.md) | Covered | Docs-only release; packaging/release channels documented. | +| 0.3.0: ratatui 0.30, scrollable help, popup frames, ssh_config `Include`, hot reload, comment-preserving saves, installable crate; security fixes (launcher `<`/`>` rejection, export newline flattening, askpass passphrase) | [Quickstart](quickstart.md), [Architecture](architecture/overview.md), [Data model](architecture/data-model.md), [Secrets](security/secrets.md), [Hosts, Groups & Identities](domain/hosts-identities.md) | Covered | `conf_val` CR/LF flattening and askpass staging are documented on the secrets page; the launcher `<`/`>` guard was superseded by the 0.10.0 removal of the external launcher. | +| 0.2.0: Embedded sessions, tab strip, keybindings, nested groups, spinner/failure screens, tag filter, ping stats | [Sessions & SFTP](workflows/sessions-sftp.md), [TUI dashboard](workflows/tui.md), [Runtime architecture](architecture/overview.md), [Hosts, Groups & Identities](domain/hosts-identities.md) | Covered | Session lifecycle, `App::shutdown_all` cleanup, and the `AppMode` state machine documented. | +| 0.1.0: TUI launcher foundation (hybrid sources, tunnels, keys, audit, fuzzy search, hot reload) | [Quickstart](quickstart.md), [Runtime architecture](architecture/overview.md), [Data model](architecture/data-model.md), [Hosts, Groups & Identities](domain/hosts-identities.md) | Covered | Architecture, storage, and domain pages cover the foundation. | ## Audit protocol 1. Read every release and `Unreleased` section in `CHANGELOG.md`. 2. Split entries by feature or behavior change and inspect implementation plus relevant tests. 3. Link each reviewed entry to exact concept pages; mark `Pending` only when the current wiki is materially incomplete. -4. Keep this notebook’s run metadata and unresolved list current. +4. Keep this notebook's run metadata and unresolved list current. + +## Known unresolved remainders + +These are real gaps that the wiki cannot close by editing, because the source itself carries them: + +- **Host-sync design is design-only** (`docs/host-sync-design.md`, epic #13): the P2P sync feature has no implementation anywhere in `src/` — no page exists because there is no system to document, only a reviewed design. +- **Detached tunnel PID races** (`src/tunnel/spawn.rs::spawn_detached_tunnel`): no advisory lock around the stale-PID/port checks and the PID write, and liveness via bare `kill(pid, 0)` with no start-time identity check — documented as a known limitation in [tunnels](workflows/tunnels.md), behavior may change. +- **`AskpassSecret` stale file on SIGKILL** (`src/session/askpass.rs`): the staged secret file is removed on `Drop` only, so `SIGKILL` leaves a `0600` file behind with no atexit cleanup — noted in [secrets](security/secrets.md) and the quickstart backlog. ## Last audit -- `gitHead`: `6e10d8891834a4e32c6850d0443460022e06c77c` -- `auditedAt`: `2026-08-03` -- `model`: `openai/gpt-5.6-luna` -- `unresolved`: 0.11.0 public-key push/key generation; npm installation details; 0.11.0 motion and remaining regression fixes; 0.10.0 panel focus/zoom and PuTTY/mRemoteNG import; 0.4.0 relicensing history +- `gitHead`: `7c69f7e4242acc8ee9a717ad7e7f7edfd796ee99` +- `auditedAt`: `2026-09-11` +- `model`: `z-ai/glm-5.3-flash` +- `unresolved`: host-sync design (design-only, unimplemented); detached tunnel PID-file races; AskpassSecret SIGKILL-stale-file caveat. diff --git a/openwiki/domain/hosts-identities.md b/openwiki/domain/hosts-identities.md index c41a0ab8..05e8c9b1 100644 --- a/openwiki/domain/hosts-identities.md +++ b/openwiki/domain/hosts-identities.md @@ -1,42 +1,154 @@ --- type: Domain Concept -title: Hosts, Groups & Identities — host sources, nested groups, Favorites, and ssh-agent identities -description: Core SSHub domain concepts — hosts from two sources (managed launcher rows and read-only ~/.ssh/config), nested groups with a reserved Favorites group and M:N membership, identities wrapping SSH keys with ssh-agent integration, OS auto-detection, and Termius import. +title: Hosts, Groups & Identities — host sources, nested groups, Favorites, identities, and imports +description: Core SSHub domain concepts — hybrid hosts (managed launcher rows, synced ssh_config rows, read-only legacy aliases), nested groups with a reserved Favorites group, identities with ssh-agent integration plus key generation and public-key push, OS auto-detection, and the ssh/Termius/PuTTY/mRemoteNG import formats. resource: src/store/types.rs -tags: [domain, hosts, groups, identities, ssh-agent, termius] +tags: [domain, hosts, groups, identities, ssh-agent, keygen, termius, putty, mremoteng] +verified: + - by: openwiki/0.5.1 + at: 2026-09-11T12:46:43.817Z +sources: + - id: openwiki-source-91868970fd3ccc66c2319a2f + resource: repo://docs/oracle-tests.md + - id: openwiki-source-ecf23cb964455b5721cf8ef2 + resource: repo://src/app/connect.rs + - id: openwiki-source-c5d004fcd8ef1c2284edaaf0 + resource: repo://src/app/identities.rs + - id: openwiki-source-6c9f0a05740b35019d73ef77 + resource: repo://src/app/keygen.rs + - id: openwiki-source-a43ecd76250acdab98621877 + resource: repo://src/app/mod.rs + - id: openwiki-source-c0d718e8edd8adec04ab3210 + resource: repo://src/app/push_key.rs + - id: openwiki-source-066740c1fdbeed847cf52c24 + resource: repo://src/cli/inventory.rs + - id: openwiki-source-faa81a0468b230404a8b1a1f + resource: repo://src/credentials.rs + - id: openwiki-source-0c12f9f256442fb9fb90951c + resource: repo://src/hosts/loader.rs + - id: openwiki-source-18c1e251293ee5d8bd127358 + resource: repo://src/import/mod.rs + - id: openwiki-source-6ba91c2b71cf890d1dc8d602 + resource: repo://src/import/mremoteng.rs + - id: openwiki-source-5cad0621d605640732d536f8 + resource: repo://src/import/putty.rs + - id: openwiki-source-8c30447f0ae8f6bc6153e0eb + resource: repo://src/import/termius_csv.rs + - id: openwiki-source-38dc2802701bc3b7c43855d1 + resource: repo://src/keybinds.rs + - id: openwiki-source-b9079954c10a783a989073f6 + resource: repo://src/known_hosts.rs + - id: openwiki-source-ed8bf05e307c6278442542c2 + resource: repo://src/lib.rs + - id: openwiki-source-bc70e4d343b4dbde35d4da4f + resource: repo://src/osinfo/detect.rs + - id: openwiki-source-bf6cda76234f60dd7eecaa7f + resource: repo://src/session/mod.rs + - id: openwiki-source-26336858a76eda68d39a06aa + resource: repo://src/session/render.rs + - id: openwiki-source-85691a4fbc8f3ccea0b4cd7c + resource: repo://src/ssh/agent.rs + - id: openwiki-source-1177e0e7ee3933b36ebabc4f + resource: repo://src/ssh/export.rs + - id: openwiki-source-8c90e650e97e8c2db8feaa52 + resource: repo://src/ssh/host.rs + - id: openwiki-source-5b3673a353d43a16cf57dfdb + resource: repo://src/ssh/import.rs + - id: openwiki-source-05c489e69ad06a37c8e614a1 + resource: repo://src/ssh/keyfile.rs + - id: openwiki-source-68e7d9439f37ca91f033f425 + resource: repo://src/ssh/probe.rs + - id: openwiki-source-661a6d64d056120219351adf + resource: repo://src/ssh/resolver.rs + - id: openwiki-source-ef7bbf76631c2cc5e3fe2e35 + resource: repo://src/store/hosts.rs + - id: openwiki-source-1ae354c1ebc2ddac5f66e8fc + resource: repo://src/store/identities.rs + - id: openwiki-source-b829ac603b13b9d0bcc902a4 + resource: repo://src/store/migrate.rs + - id: openwiki-source-c9bbdb6598e3836ed3d2ce0e + resource: repo://src/store/types.rs + - id: openwiki-source-53fcad5479fc2b910ea41be5 + resource: repo://src/tui/widgets/middle_stack.rs + - id: openwiki-source-8b4c4c48f43ad9159432ae53 + resource: repo://tests/e2e/keychain.rs + - id: openwiki-source-caa0ad05ad3f654cbccffc08 + resource: repo://tests/e2e/termius_import.rs + - id: openwiki-source-63175631717458c0fde9b563 + resource: repo://tests/smoke/cli_commands.rs +generated: { by: "openwiki/0.5.1", at: "2026-09-11T12:46:43.817Z" } --- # Hosts, Groups & Identities ## Hosts + A host is the central entity. Storage and merging rules live in [data model](../architecture/data-model.md#hybrid-host-model); the user-facing model: - **Managed hosts** (`HostSource::Launcher`) — full CRUD from the [host form](../workflows/tui.md) or [CLI](../workflows/cli.md); fields include address, port, username, tags, description, environment, per-host session-logging override, and `transport` (ssh/mosh). -- **ssh_config hosts** (`HostSource::SshConfig`) — imported/synced from `~/.ssh/config`; editable metadata but the connection fields track the config file (hot-reloaded by the [file watcher](../architecture/overview.md#file-watcher)). + +- **ssh_config hosts** (`HostSource::SshConfig`) — imported/synced from `~/.ssh/config`; editable metadata but the connection fields track the config file (hot-reloaded by the [file watcher](../architecture/overview.md#file-watcher)); a row is re-synced only when `compute_ssh_config_hash` reports drift. - **Legacy aliases** — ssh_config entries with no DB row; surfaced read-only with metadata from `metadata.db`. -Resolution always goes through `HostResolver` / `SshConfigResolver` (`src/ssh/resolver.rs`), which lists `Host` aliases (following `Include`, depth-capped at 16) and resolves effective options with `ssh -G`. `build_ssh_argv` / `build_mosh_argv` (`src/ssh/host.rs`) turn a resolved host into the spawn argv used by [embedded sessions](../workflows/sessions-sftp.md). +`load_merged_hosts` (`src/hosts/loader.rs`) merges the three: sync ssh_config rows, load both DB sources, then append aliases that have no DB row. `upsert_ssh_config_host` never overwrites a `source=launcher` row (it returns `SkippedLauncher`), so imports and syncs can never clobber hosts the user created themselves; updates to ssh_config rows preserve user metadata (tags, notes, favorite). -**OS auto-detection** (`src/osinfo/`): on first connect a background worker runs `cat /etc/os-release || uname -s` over ssh (BatchMode without a secret, askpass with one), `parse_os` maps it to a canonical id stored in `hosts.os_icon`, and the host card renders a vendored ANSI/Braille logo (`OsLogoWidget`). Failures are silent by design. +Resolution always goes through `HostResolver` / `SshConfigResolver` (`src/ssh/resolver.rs`), which lists `Host` aliases (following `Include`, depth-capped at 16, with `*`/`?` glob support in the final path component) and resolves effective options with the real `ssh -G` (`hostname`, `user`, `port`, `proxyjump`, `identityfile`, `forwardagent`, `remotecommand`). The config path comes from `~/.ssh/config` unless `SSHUB_SSH_CONFIG` (fallback `SSH_LAUNCHER_SSH_CONFIG`) overrides it. Since 0.14.0, quoted aliases are unquoted the way OpenSSH unquotes them (`Host "web"` lists as `web`); before that, `ssh -G` rejected the quoted alias with `hostname contains invalid characters`, so the host showed up but could not be connected to. The hand-rolled `Host`-line parser is the one part of resolution sshub does itself (OpenSSH has no "enumerate hosts" mode), so it is pinned by a differential test that resolves every listed alias through the real `ssh` binary, per the oracle-test rule in `docs/oracle-tests.md`. `build_ssh_argv` / `build_mosh_argv` (`src/ssh/host.rs`) turn a resolved host into the spawn argv used by [embedded sessions](../workflows/sessions-sftp.md). + +**Option-like values are refused at the store, not just at connect.** A host field starting with `-` is read by ssh as a flag, not a value (an address of `-oProxyCommand=id` runs `id` locally — issue #101). `create_host` / `update_host` therefore refuse a leading-dash `name`, `address` or `username` (`reject_option_like`; `is_option_like` is exposed so the host form can flag the value while the form is still open). Importers treat one refusal as one dropped row: the poisoned entry is skipped, the rest of the file lands, and the refusal is counted in the report (`skipped_invalid`). `safe_ssh_target` is the connect-time backstop: it rewrites a leading-dash target to the `ssh://` form OpenSSH refuses outright, so a row that predates the write guard fails loudly instead of executing anything. + +### OS auto-detection (`src/osinfo/`) + +On first connect, a background worker probes managed hosts whose `os_icon` is still empty. `connect_host_entry` sends an `OsDetectCmd` only when the host id is not already in the `os_detect_inflight` set, so a rapid re-connect never spawns duplicate probes. The worker thread (same mpsc shape as the ping worker, test seam via the `ProbeRunner` trait) shells a short non-interactive ssh: `cat /etc/os-release 2>/dev/null || uname -s` with `ConnectTimeout=8` and `BatchMode=yes` when there is no stored secret (askpass staging when there is). `parse_os` maps the output to a canonical id; only then is an `OsDetectEvent::Detected` emitted, drained on the UI tick and applied by `apply_os_detect` — persist the canonical id to `hosts.os_icon`, clear the inflight marker, reload hosts. Failures and unrecognised output are silent by design: no event, no notice, the `os_icon` stays empty. The host card renders a vendored ANSI/Braille logo through `OsLogoWidget`. + +```mermaid +sequenceDiagram + participant App + participant W as os-detect worker + participant S as ssh probe + App->>W: OsDetectCmd host_id argv secret + W->>S: ssh -o ConnectTimeout=8 cat /etc/os-release + S-->>W: stdout or None on failure + alt canonical os id recognised + W-->>App: OsDetectEvent Detected + App->>App: persist os_icon and reload hosts + else probe failed or output unparsed + W-->>App: nothing (silent by design) + end +``` + +*The OS auto-detect worker: connect kicks off an inflight-guarded probe; only a recognised OS id produces an event that is persisted to `hosts.os_icon`.* ## Groups & Favorites -`host_groups` are **nested** (`parent_id`) and a host can belong to **multiple groups** via `host_group_memberships`. A `reserved` flag marks the built-in **Favorites** group — it's found by flag, never by name, so a user's pre-existing "Favorites" group is never hijacked; `f` toggles favorite and a ★ marker shows in the list. Managed from the group manager (`Shift+G`) or `sshub group …`. Shipped in 0.7.0 per `docs/superpowers/specs/2026-07-10-multi-group-favorites.md`. +`host_groups` are **nested** (`parent_id`) and a host can belong to **multiple groups** via `host_group_memberships`. A `reserved` flag marks the built-in **Favorites** group — it is found by flag (`reserved = 1`), never by name, so a user's pre-existing "Favorites" group is never hijacked: if a user group already owns the name, the reserved group is created as "Favorites (2)" and the user's group stays untouched. Reserved groups cannot be renamed or deleted. `f` toggles favorite and a ★ marker shows in the list; the flag and the membership are kept in sync by `sync_favorite_membership` on every write path, and `ManagedHost::favorite` is derived at load from reserved-group membership. `set_host_groups` replaces only *non-reserved* memberships, so saving the host form never drops favorite status. + +When a host belongs to more than one non-reserved group and has no identity of its own, `materialize_identity` copies the primary group's `default_identity_id` onto the host row, so an inherited identity stays stable across later membership changes. Favouriting a host (which joins the reserved group) never triggers materialization. Managed from the group manager (`Shift+G`) or `sshub group list|show|add|edit|delete`. Shipped in 0.7.0 per `docs/superpowers/specs/2026-07-10-multi-group-favorites.md`. ## Identities -An identity (`src/store/identities.rs`) bundles a display name, username, and private key path; a "Default" identity is seeded. Hosts/groups reference identities for connection defaults. Secrets (key passphrases, host passwords) live in the OS keyring keyed `identity:{id}` / `host:{id}` — see [secrets](../security/secrets.md). +An identity (`src/store/identities.rs`) bundles a display name, username, private key path, and an optional certificate path; a "Default" identity is seeded. Hosts reference identities via `identity_id`; groups carry a `default_identity_id` that materializes as described above. Secrets (key passphrases, host passwords) live in the OS keyring keyed `identity:{id}` / `host:{id}` — see [secrets](../security/secrets.md). Deleting an identity that hosts still reference is refused (`DeleteIdentityOutcome::InUse` with the host count). + +- **ssh-agent** (`src/ssh/agent.rs`) — wrappers over `ssh-add -l` / `-d` (add runs plain `ssh-add path`); `detect_agent` reads `SSH_AUTH_SOCK` and parses the listing into type/bits/fingerprint/comment. The Keys tab shows loaded status and can add/remove keys (`p` / `r`; CLI: `sshub identity agent-remove`). Zoomed, the agent panel keeps the socket/forward-agent/config header and lists every loaded key, one selectable row per key (removable with `d`). +- **Key files** (`src/ssh/keyfile.rs`) — `ssh-keygen -y` probing detects whether a key needs a passphrase (`key_is_encrypted`) and whether a candidate passphrase decrypts it (`passphrase_matches`); both fail open (return `None`) when ssh-keygen is missing or the error is unrelated, so callers never block on an unknown. Every passphrase-carrying `ssh-keygen` invocation — probing, generation, public-key extraction — stages the secret through a `KeygenAskpass` helper: a `0600` secret file plus a `0700` script exported as `SSH_ASKPASS`, both removed on drop, so the passphrase never appears in `ps` argv. +- **Key generation** (`src/app/keygen.rs`) — `g` on the Keys tab opens a form for key type (Ed25519 or RSA-4096), passphrase, comment and target path. `generate_key_pair` refuses to overwrite an existing private key or its `.pub`. On success the identity is registered immediately (named after the comment, else the filename), the Keys tab reloads with it selected, and an optional passphrase is stored under `identity:{id}`. +- **Public-key push** (`src/app/push_key.rs`) — `Shift+P` from the hosts list opens an identity picker; from the Keys tab it opens a host picker. The public key is read from the `.pub` file when one exists, otherwise extracted with `ssh-keygen -y` and the identity passphrase staged via askpass. The remote command runs under `umask 077`, creates `~/.ssh/authorized_keys` if missing, and appends only when the exact line is not already present, so repeating the push is harmless. The push runs as a real session reusing the stored-credential path and both the attempt and its failures are audited. +- **Probing** (`src/ssh/probe.rs`) — defines `SshLogEntry`/`LogLevel`, populated by manual log pushes from the connect/session paths. The module's own background `ssh -v BatchMode` classifier (`spawn_ssh_probe`/`classify_line`), which used to periodically probe every known host, is dead code today (no callers) and was disabled because it "buried the events the user actually cares about" (`src/app/mod.rs`); the SSH log is reserved for user-initiated events and is capped at 200 entries. There is no live auth-method display in the detail panel; the server host-key fingerprint is shown on the connect screen, parsed from ssh's `-v` stderr. + +## Importing hosts -- **ssh-agent** (`src/ssh/agent.rs`) — wrappers over `ssh-add -l` / `-d`; the Keys tab shows loaded status and can add/remove keys (`p` / `r`; CLI: `sshub identity agent-remove`). -- **Key files** (`src/ssh/keyfile.rs`) — `ssh-keygen -y` probing detects whether a key needs a passphrase; the passphrase is fed through a staged 0600/0700 askpass script so it never appears in `ps` argv. -- **Probing** (`src/ssh/probe.rs`) — defines `SshLogEntry`/`LogLevel`, populated by manual log pushes from the connect/session paths. The module’s own background `ssh -v BatchMode` classifier (`spawn_ssh_probe`/`classify_line`), which used to periodically probe every known host, is dead code today (no callers) and was disabled because it "buried the events the user actually cares about" (src/app/mod.rs); there is no live auth-method/host-key display in the detail panel. +`sshub import --from ssh|termius|putty|mremoteng [--dry-run]`. `--dry-run` parses and prints a preview without touching the store; it is supported for `termius`, `putty` and `mremoteng` and explicitly refused for `--from ssh`. -## Termius import (`src/import/termius_csv.rs`) +- **ssh** (`src/ssh/import.rs`) — resolves every listed alias and upserts `source=ssh_config` rows; launcher rows are skipped and counted. +- **Termius** (`src/import/termius_csv.rs`) — imports Termius backups (`L00t.csv` + `ssh_keys/` directory — format documented in `docs/termius-export-format.md`) as managed hosts and identities. Passwords/passphrases are re-stored into the keyring with **write-verification** (`store_credential_verified` sets the value and reads it back); failures surface as `keyring_failures` in the import report rather than silently dropping secrets. Key material is copied to `~/.ssh/termius_`. TUI: `Shift+T`; covered by `tests/e2e/termius_import.rs`. +- **PuTTY** (`src/import/putty.rs`) — accepts a Windows `regedit /e` `.reg` export (UTF-16LE with BOM, `dword` ports) or a `~/.putty/sessions` directory (plain `Key=Value` files, decimal ports). Session names are percent-decoded (`My%20Server` → `My Server`). Only connections whose `Protocol` is `ssh` (or absent — historically the default) are imported; RDP/telnet/raw entries are counted in `skipped_non_ssh`, and the `Default Settings` pseudo-session (empty `HostName`) is dropped. Imported rows are launcher hosts with `notes = "Imported from PuTTY"`. +- **mRemoteNG** (`src/import/mremoteng.rs`) — parses `confCons.xml` with a hand-rolled XML reader (no XML crate, matching the house style). `Type="Container"` nodes form a folder breadcrumb that becomes the host's tags; `Type="Connection"` nodes with an `SSH*` protocol become hosts; every other protocol is counted as skipped. Encrypted `Password` attributes are **not decrypted** — these importers create hosts only and carry no stored secret. Rows are tagged `Imported from mRemoteNG`. -Imports Termius backups (`L00t.csv` + `ssh_keys/` directory — format documented in `docs/termius-export-format.md`) as managed hosts and identities. Passwords/passphrases are re-stored into the keyring with **write-verification**; failures surface as `keyring_failures` in the import report rather than silently dropping secrets. TUI: `Shift+T`; covered by `tests/e2e/termius_import.rs`. +All three third-party importers share `HostImportReport` (`imported`, `skipped_existing`, `skipped_non_ssh`, `skipped_invalid`): hosts whose name already exists are skipped, never overwritten, and a row the store refuses (leading-dash field, issue #101) is dropped on its own without costing the rest of the file. ## Change guidance -- Host CRUD invariants (dedupe with ssh_config sources, favorite semantics) are pinned by `tests/e2e/host_crud.rs`, `host_sort.rs`, `group_crud.rs`, `hybrid_compat.rs`, `ssh_config_sync.rs`. +- Host CRUD invariants (dedupe with ssh_config sources, favorite semantics, group nesting) are pinned by `tests/e2e/host_crud.rs`, `host_sort.rs`, `group_crud.rs`, `hybrid_compat.rs`, `ssh_config_sync.rs`. - Import/sync must never overwrite `source=launcher` rows and never write the user's own `~/.ssh/config` (export goes to `exported.conf`). +- Keygen and push-key flows are pinned by `tests/e2e/keychain.rs` (including the group-header and identity-picker edge cases); Termius import by `tests/e2e/termius_import.rs`; PuTTY and mRemoteNG imports by `tests/smoke/cli_commands.rs`. +- The resolver's alias listing is pinned by a differential test against the real `ssh` binary (`listed_aliases_round_trip_through_real_ssh`), which degrades — not fails — when `ssh` is absent. diff --git a/openwiki/domain/index.md b/openwiki/domain/index.md index 5b509779..1ee8cb96 100644 --- a/openwiki/domain/index.md +++ b/openwiki/domain/index.md @@ -1,3 +1,3 @@ # Files -- [Hosts, Groups & Identities — host sources, nested groups, Favorites, and ssh-agent identities](hosts-identities.md) - Core SSHub domain concepts — hosts from two sources (managed launcher rows and read-only ~/.ssh/config), nested groups with a reserved Favorites group and M:N membership, identities wrapping SSH keys with ssh-agent integration, OS auto-detection, and Termius import. +- [Hosts, Groups & Identities — host sources, nested groups, Favorites, identities, and imports](hosts-identities.md) - Core SSHub domain concepts — hybrid hosts (managed launcher rows, synced ssh_config rows, read-only legacy aliases), nested groups with a reserved Favorites group, identities with ssh-agent integration plus key generation and public-key push, OS auto-detection, and the ssh/Termius/PuTTY/mRemoteNG import formats. diff --git a/openwiki/index.md b/openwiki/index.md index 7d71e217..b67d4093 100644 --- a/openwiki/index.md +++ b/openwiki/index.md @@ -1,10 +1,10 @@ --- -okf_version: "0.1" +okf_version: "0.2" --- # Files -- [Changelog to OpenWiki Coverage](changelog-coverage.md) - Ledger of every CHANGELOG.md feature, fix, and behavior change reviewed against source evidence and mapped to OpenWiki pages. +- [Changelog to OpenWiki Coverage](changelog-coverage.md) - Ledger of every CHANGELOG.md feature, fix, and behavior change from 0.1.0 through 0.16.0 (plus Unreleased) reviewed against source evidence and mapped to the OpenWiki page that documents it, with Covered/Pending status and run metadata. - [SSHub — Quickstart](quickstart.md) - Entry point to the SSHub knowledge base. SSHub is a Rust terminal UI for managing and connecting to SSH hosts, combining ~/.ssh/config with a built-in SQLite host database, embedded PTY sessions, SFTP, tunnels, identities, and an audit log. # Directories diff --git a/openwiki/integrations/external-terminals.md b/openwiki/integrations/external-terminals.md index 4df83362..9f02246b 100644 --- a/openwiki/integrations/external-terminals.md +++ b/openwiki/integrations/external-terminals.md @@ -1,25 +1,361 @@ --- type: Integration -title: Integrations — terminal stream demos and external touchpoints -description: SSHub's current integration surface includes embedded PTY demo recording, OS keyring, SSH tooling, and GitHub/crates.io automation; the former external-terminal launcher was removed in 0.10.0. +title: Integrations — external touchpoints and demo pipeline +description: SSHub's current external surface — the removed external-terminal launcher (only ignored legacy config keys remain since 0.10.0), the npm shim and packaging pipeline (sshub-tui + platform optional dependencies, SSHUB_INSTALL_CHANNEL=npm), the terminal-stream demo recorder in demo/, and the external tools SSHub drives (OS keyring, ssh-agent, ssh -G, ssh-keygen, known_hosts, third-party host exports, GitHub Actions/crates.io). resource: demo/record.py -tags: [integrations, terminal, demo, asciicast] +tags: [integrations, npm, demo, asciicast, external-tools, packaging] +verified: + - by: openwiki/0.5.1 + at: 2026-09-11T12:46:43.817Z +sources: + - id: openwiki-source-4d1d392666be6dfdd7a91a2e + resource: repo://.github/workflows/release.yml + - id: openwiki-source-651d1fb6c9e49916a916ab51 + resource: repo://Cargo.toml + - id: openwiki-source-ca6cb4b1a14fd7969dfae3ec + resource: repo://CHANGELOG.md + - id: openwiki-source-d558d73911c76fad7c08b4c8 + resource: repo://demo/.gitignore + - id: openwiki-source-dc8d1707c59195c6092b1938 + resource: repo://demo/bin/ssh + - id: openwiki-source-6432e9b54a52fa8c18f77f0c + resource: repo://demo/record.py + - id: openwiki-source-169a4bc1e318936c0823b1b1 + resource: repo://demo/seed_demo.rs + - id: openwiki-source-a0d3faa87048e4766a5954a1 + resource: repo://demo/seed-demo.sh + - id: openwiki-source-feac1d6b39bb341aa3362c9f + resource: repo://demo/sftp-server.sh + - id: openwiki-source-894f7168bd5e9e5aa1dcd57a + resource: repo://npm/build.sh + - id: openwiki-source-29f2ffe501d0bdc557fcc14a + resource: repo://npm/wrapper/bin/sshub.js + - id: openwiki-source-fe697126247103d981f7863b + resource: repo://npm/wrapper/package.json.in + - id: openwiki-source-58f2837f702d3f604de2bc20 + resource: repo://npm/wrapper/README.md + - id: openwiki-source-2a737474d86fc75cc9d9694f + resource: repo://src/config.rs + - id: openwiki-source-faa81a0468b230404a8b1a1f + resource: repo://src/credentials.rs + - id: openwiki-source-18c1e251293ee5d8bd127358 + resource: repo://src/import/mod.rs + - id: openwiki-source-6ba91c2b71cf890d1dc8d602 + resource: repo://src/import/mremoteng.rs + - id: openwiki-source-5cad0621d605640732d536f8 + resource: repo://src/import/putty.rs + - id: openwiki-source-8c30447f0ae8f6bc6153e0eb + resource: repo://src/import/termius_csv.rs + - id: openwiki-source-b9079954c10a783a989073f6 + resource: repo://src/known_hosts.rs + - id: openwiki-source-ed8bf05e307c6278442542c2 + resource: repo://src/lib.rs + - id: openwiki-source-bf6cda76234f60dd7eecaa7f + resource: repo://src/session/mod.rs + - id: openwiki-source-ec2a6161f71baa54aa60a1c0 + resource: repo://src/sftp/transport.rs + - id: openwiki-source-85691a4fbc8f3ccea0b4cd7c + resource: repo://src/ssh/agent.rs + - id: openwiki-source-8c90e650e97e8c2db8feaa52 + resource: repo://src/ssh/host.rs + - id: openwiki-source-05c489e69ad06a37c8e614a1 + resource: repo://src/ssh/keyfile.rs + - id: openwiki-source-661a6d64d056120219351adf + resource: repo://src/ssh/resolver.rs + - id: openwiki-source-6670510146d9300d4d414ad7 + resource: repo://src/tui/widgets/tab_bar.rs +generated: { by: "openwiki/0.5.1", at: "2026-09-11T12:46:43.817Z" } --- # Integrations -## External-terminal launcher status +SSHub deliberately has a small runtime integration surface: the embedded PTY is the +only session transport, secrets go through the OS keyring, and the `ssh` binary does +the connecting. Everything else on this page — the npm shim, the demo recorder — is +packaging and contributor/release tooling, plus the external tools the app shells out +to. -The retired Kitty/Ghostty/custom `TerminalLauncher` subsystem and its `terminal` / `launch_command` configuration keys were removed in 0.10.0. Embedded [PTY sessions](../workflows/sessions-sftp.md) are the connection transport. Old configuration files remain loadable because those legacy keys are ignored; do not add new runtime behavior under the removed `src/launcher/` path. +## External-terminal launcher status (removed) -## Demo pipeline (`demo/`) +The retired Kitty/Ghostty/custom-command `TerminalLauncher` subsystem was removed in +0.10.0 and is **fully deleted**: no trait, no `src/launcher/` directory, and no test +doubles remain in the tree. The only residue is the two ignored legacy config keys — +`terminal` and `launch_command` — which older `config.toml` files may still carry. +Those files keep loading because the config model does not deny unknown fields; the +regression test `parse_config_applies_overrides` (`src/config.rs`) pins exactly that: +a config carrying the removed keys parses fine and the rest of the settings still +apply (issue #30). -README GIFs and screenshots are now recorded from the terminal byte stream rather than timer-sampled screenshots. `demo/record.py` drives the scenarios, records timestamped asciicast output, and renders it with `agg`; timing checks compare each take with the scenario's expected duration. This preserves the animation timeline at 1.00x and avoids VHS capture-rate distortion. The tapes keep reduced-motion disabled so the motion pass is visible. +There is no external launch path to extend or re-enable. [Embedded PTY +sessions](../workflows/sessions-sftp.md) are the connection transport (per-host `ssh` +or `mosh`; tunnels and SFTP are ssh-only). Do not add runtime behavior under the +removed `src/launcher/` path — `wrap_script_command` in `src/session_log.rs`, which +wraps external command spawns in `script(1)` for session logging, is the closest +surviving concept and belongs where it is. -The fixture home (`demo/home/` plus mock commands in `demo/bin/`) is seeded by `demo/seed-demo.sh` and the `seed-demo` Cargo example. `demo/sftp-server.sh` supports transfer scenarios. Generated GIFs and screenshots live under `demo/gifs/` and `demo/screenshots/`; the demo tooling is contributor/release infrastructure, not product runtime. +## npm distribution (`npm/`) -## Other external touchpoints +### The shim (`npm/wrapper/bin/sshub.js`) -- **OS keyring / Secret Service** — see [secrets](../security/secrets.md). -- **ssh-agent, ssh -G, ssh-keygen, known_hosts, and Termius backups** — see [hosts & identities](../domain/hosts-identities.md) and the [known-hosts manager](../workflows/known-hosts.md). -- **GitHub Actions / crates.io** — see [CI & automation](../operations/ci-cd.md). +The npm wrapper package is `sshub-tui`, not `sshub` — npm refuses the bare name as +too close to the existing `ssh2` and `sshpk` — but the command it installs is still +`sshub` (bin name is independent of package name). The real binary lives in one of +three platform-specific optional dependencies that npm installs only where its +`os`/`cpu` match: + +| Platform key | Package | +|---|---| +| `linux-x64` | `sshub-linux-x64` | +| `darwin-arm64` | `sshub-darwin-arm64` | +| `darwin-x64` | `sshub-darwin-x64` | + +The shim never downloads anything; on each run it: + +1. Looks up `${process.platform}-${process.arch}` in the fixed package map. An + unsupported platform dies with a *build from source: `cargo install sshub`* + hint; a missing optional dependency (`--no-optional` installs, interrupted + installs) dies with a reinstall hint. +2. Resolves the platform package's `bin/sshub` with `require.resolve`. +3. **Repairs the executable bit**: npm normally preserves it, but a zip-based store + or an over-eager umask can drop it; the shim checks `X_OK` and, failing that, + `chmodSync(bin, 0o755)` — cheaper to fix than to explain. If the repair is + refused, it dies. +4. Spawns the binary with `spawnSync` and `stdio: 'inherit'` so the child gets the + real TTY (the TUI needs it) and shares the process group, so Ctrl+C reaches the + binary from the terminal rather than through the shim. If the child dies by + signal, the shim re-raises the same signal on itself so the shell sees the real + cause; otherwise it exits with the child's status (1 when absent). + +Before spawning it exports `SSHUB_INSTALL_CHANNEL=npm`. This marker exists because +the shipped binary is **byte-identical to the release-tarball binary** — `npm +build.sh` repacks the very tarballs the release workflow attached (below) — so no +path heuristic can tell an npm install from a GitHub-release install. The env var is +the only honest signal. + +### Install-channel detection (`src/tui/widgets/tab_bar.rs`) + +The tab bar shows the resolved version at the far right, with the detected install +channel as a suffix so a support conversation can tell how the binary got onto the +machine. Resolution order, most specific first: + +- `SSHUB_INSTALL_CHANNEL` set and non-empty → that value verbatim (the npm shim sets + `npm`). +- The binary lives in cargo's install bin dir (`$CARGO_HOME/bin`, else + `~/.cargo/bin`) → `cargo`. +- The binary sits inside a build-target dir (`target/`, or a custom `…-target/` via + `CARGO_TARGET_DIR`) → `source` (a `cargo run`/`cargo build` out of the checkout); + the same applies under `~/.local/bin` (the `just install` destination). +- Anything else (distro package, manual copy) → no channel, no suffix. + +The version label itself follows `SSHUB_VERSION_LABEL`: unset → `v{CARGO_PKG_VERSION}` +plus the ` · {channel}` suffix when a channel was detected; set but empty → the label +is hidden entirely; set and non-empty → that exact string verbatim, no suffix. Demo +recordings set it empty so the GIFs never advertise a stale version (see below). + +### Assembling and publishing (`npm/build.sh`) + +`npm/build.sh` compiles nothing. It downloads the `sshub-*.tar.gz` release assets for +the version (from `gh release download v$version`, or `TARBALL_DIR` when CI supplies +workflow artifacts), extracts each into a platform package (`bin/sshub` chmod 755, +plus `package.json`/README/LICENSE rendered from `npm/platform/*.in`), copies the +wrapper's `bin/sshub.js` into the `sshub-tui` package, and renders the wrapper +manifest from `npm/wrapper/package.json.in` (bin `sshub` → `bin/sshub.js`, engines +node ≥ 18, `optionalDependencies` pinned to the three platform packages). Verification +on a Linux x86_64 host runs the extracted binary's `--version` — which must equal +`sshub $version`, catching packaging that points at a different release than it +claims — then mirrors a real install (`node_modules/sshub-linux-x64` symlink) and +drives the shim exactly the way `npx sshub` would, requiring the same version +output. All four packages get an `npm pack --dry-run`. + +Publishing order matters: the three platform packages go first and `sshub-tui` last, +because the wrapper's `optionalDependencies` must already exist in the registry — +published the other way round, the wrapper resolves to no binary at all and fails for +everyone installing in that window. Re-runs skip versions already in the registry +(registry versions are immutable, so a re-run after a partial failure skips what +landed instead of dying on *cannot publish over the previously published version*). +The [release workflow](../operations/ci-cd.md) publishes via **OIDC trusted +publishing** (`id-token: write`, npm ≥ 11.5.1) rather than a long-lived `NPM_TOKEN`, +with npm attaching provenance itself; each of the four packages needs a trusted +publisher configured on npmjs.com pointing at `release.yml`. crates.io publishing +(`cargo publish` with the `CARGO_REGISTRY_TOKEN` secret) runs as a separate job of +the same workflow. + +## Demo pipeline (`demo/`) — contributor/release tooling + +The `demo/` tree is contributor/release infrastructure for the README media, not +product runtime: it is excluded from the published crate (`Cargo.toml` `exclude`) +together with the generated media, and the README embeds `demo/gifs/*.gif` and +`demo/screenshots/*.png` from GitHub URLs. + +```mermaid +flowchart TD + Start["just record-gifs scenarios"] --> Build["cargo build --release when the binary is missing"] + Build --> Seed["bash demo/seed-demo.sh seeds launcher.db"] + Seed --> Env["per scenario env_for: fresh config dir plus throwaway HOME"] + Env --> Setup{"scenario needs setup"} + Setup -->|"yes"| Srv["bash demo/sftp-server.sh start"] + Setup -->|"no"| Run["spawn target/release/sshub under a PTY and feed the scripted keystrokes"] + Srv --> Run + Run --> Cast["write asciicast v2 events tagged with their write time"] + Cast --> Trim["trim events at the measured resync point"] + Trim --> Pace["pacing check: recorded vs scripted duration must stay within 0.1x of 1.00x"] + Pace --> Agg["agg renders the GIF honoring the timestamps"] + Agg --> Stills["ffmpeg extracts each marked still from the rendered GIF"] + Stills --> Out["demo/gifs plus demo/screenshots feed the README"] +``` + +*The demo recording pipeline: seed once, record the PTY byte stream per scenario, trim at the measured resync point, check pacing, then render GIF and stills.* + +### Why the terminal byte stream, not screenshots + +`demo/record.py` replaced the VHS tapes. VHS renders the session in a headless +browser and *screenshots* it on a timer; SSHub redraws at ~60fps while animating, the +capture rate collapsed under that, and because the video was written at a constant +frame rate the missed frames came out as a sped-up recording — measured at 0.28x real +time on `navigate`, with transitions down to three frames. Recording what the program +actually wrote, tagged with the moment it was written (asciicast v2), makes the +timeline exact by construction: nothing is sampled, idle stretches collapse into one +long frame, and `agg` renders honoring the timestamps so every take plays at 1.00x +with animations included (and about a third of the old size). + +Recording requires `agg` (`cargo install --git https://github.com/asciinema/agg`) and +ffmpeg. Run everything (`demo/record.py`) or a subset (`demo/record.py hero navigate`, +or `just record-gifs hero sftp`). + +### Mechanics + +- **`Tape`** is a keystroke timeline (`key`, `type`, `sleep`) that reads like the + `.tape` files it replaced minus the shell: the binary is started directly with its + environment already set, so there is nothing to type into a prompt and nothing to + `Wait` for. Typing paces at 40ms/char, matching the tapes' `Set TypingSpeed 40ms`. +- **`begin()` trims by resync, not by dropping events.** Trimming a byte-stream + recording is not simply dropping earlier events: ratatui writes cell diffs, so the + survivors would paint onto a screen that was never drawn (missing logo, half-there + header counters). The scheduled `resync` action narrows the PTY by one column — + `src/lib.rs` resizes all sessions on any size change, forcing a full repaint — + waits for it, restores the width, and the trim point is *measured* at that moment, + so the recording starts on a complete screen. +- **`still(name)`** markers keep a moment as a screenshot. They are scheduled like a + keystroke but measured at record time (script-time drift would catch popups + mid-animation), and each reserves a beat afterwards so the marker does not share a + timestamp with the next keystroke. Stills are extracted from the rendered GIF with + ffmpeg — the last frame at or before the mark (an idle screen emits no frames at + all, since ratatui only writes what changed, so "the next frame" is whatever moved + next, usually a popup opening). They inherit the GIF's 256-colour palette, which is + lossless in practice for a terminal palette a few dozen colours wide. +- **Pacing check:** the expected visible duration is the last scripted timestamp + minus the trim start, plus the 1.5s tail. A take is `ok` only when + recorded/expected stays within ±0.1 of 1.00x; any `OFF` take fails the run. This is + what guards the 1.00x animation timeline. +- **Rendering:** `agg` runs with `--fps-cap 50`, the Catppuccin Mocha 16-slot theme + (the palette the old tapes asked xterm.js for), `JetBrainsMono Nerd Font Mono` (the + Mono variant, or agg warns about cell metrics), and a 2s last-frame duration, at + 150×40 cells. + +### Fixture home and environment + +Each scenario runs under `env_for`: a pristine config directory +(`/tmp/sshub-demo-`, copied from `demo/home/.config/sshub/config.toml` so one +run cannot leak settings into another) and a throwaway `$HOME` +(`/tmp/sshub-demo-home`, holding a pristine `sftp-local` folder and a fresh `.ssh`) +— a real `$HOME` would publish the recorder's username and directory layout in the +README, and the SFTP browser prints the local pane's cwd on screen. +`SSHUB_VERSION_LABEL` is set empty so the version label is hidden and GIFs never +advertise a stale version, and `demo/bin` plus `target/release` are prepended to +`PATH`. Motion is deliberately left **on**: `disable_animation` is the reduced-motion +toggle and would switch off every slide these recordings exist to show (the old tapes +used it to skip the intro animation, which is why four of the five GIFs had no motion +at all); the intro is dismissed with a keypress instead. + +- `demo/seed-demo.sh` resets and seeds `demo/home/.local/share/sshub/launcher.db` by + running the `seed-demo` Cargo example (`demo/seed_demo.rs`, an example rather than + a bin so `cargo install sshub` never installs it). It wipes `launcher.db` / + `metadata.db` (plus `-wal`/`-shm`) for a deterministic re-seed, then creates the + nested groups (Production → Web, Databases; Staging) and seven hosts whose + addresses are real, pingable public anycast resolvers (Google, Cloudflare, Quad9, + OpenDNS, Level3) so cards show *online* — the actual connect is simulated. When + the SFTP server's client key exists, the seeder also adds a real + `127.0.0.1:2222` host with that key as its identity. +- `demo/sftp-server.sh start|stop|status` supports the transfer scenarios: SSHub's + SFTP browser speaks native libssh2, so it needs an actual server. The script spins + up a rootless sshd bound to `127.0.0.1:2222` (port overridable via + `SSHUB_SFTP_DEMO_PORT`) with an ephemeral ed25519 host key and client key under + `demo/home/.ssh`, pubkey-only auth, and `ForceCommand internal-sftp -d ` + so every login lands in a curated fake deploy tree instead of the recorder's real + `$HOME`; a curated local folder is regenerated too. Because the host key is + ephemeral, `start` purges the stale `[127.0.0.1]:port` line from the recorder's + `known_hosts` (`ssh-keygen -R`) so trust-on-first-use re-records the current + ephemeral key instead of tripping the host-key-changed (MITM) guard. +- `demo/bin/ssh` shadows the real `ssh` (demo/bin is prepended to `PATH`) and renders + a believable remote session: it emits an `ssh -v`-style handshake on stderr — + including the *Authenticated to* marker that `src/session`'s `CONNECTED_NEEDLES` + latch as the only honest connected signal — then runs interactive bash with an + rcfile that fakes the target box (fake `uname`, `hostname`, `ls`, `df`, `uptime`, + prompt as `deploy@`), and redirects bash's stderr onto the PTY so the prompt + is visible. `demo/bin/cowsay` is a tiny cowsay stand-in. Do not install these on a + real `PATH`. +- Scenarios: `hero`, `navigate`, `connect`, `add-host`, `sftp` (which drives the + sftp-server helper via setup/teardown and runs with cwd `sftp-local`), and + `screenshots` (stills only — its GIF is scratch). Generated GIFs land in + `demo/gifs/` and stills in `demo/screenshots/`; per-take asciicasts land in + `demo/build/`. + +## External tools SSHub drives + +- **OS keyring / Secret Service** (`src/credentials.rs`) — the `PasswordStore` trait + seam with `OsKeyring` over the `keyring` crate (service `"sshub"`); a + `check_keyring_available` probe distinguishes a missing Secret Service provider + (`org.freedesktop.secrets` / `ServiceUnknown`) from other failures, and where no + keyring exists an owner-only `FilePasswordStore` (`credentials.json`, 0600 temp + file + atomic rename) takes over and migrates back once a keyring returns. + Details: [secrets](../security/secrets.md). +- **ssh-agent** (`src/ssh/agent.rs`) — `SSH_AUTH_SOCK` detection and `ssh-add + -l/-d` wrappers parsed into type/bits/fingerprint/comment; the Keys tab and the + CLI add/remove keys. See [hosts & identities](../domain/hosts-identities.md). +- **`ssh` / `mosh`** (`src/ssh/host.rs`) — `build_ssh_argv` / `build_mosh_argv` turn + resolved hosts into argv (port, identity, certificate, ProxyJump, ForwardAgent), + with `safe_ssh_target` rewriting leading-dash targets to an `ssh://` form OpenSSH + refuses, and `StrictHostKeyChecking=accept-new` injected when a secret is staged + or a mosh session runs. +- **`ssh -G`** (`src/ssh/resolver.rs`) — effective host options (`hostname`, `user`, + `port`, `proxyjump`, `identityfile`, `forwardagent`, `remotecommand`) are resolved + by shelling the real `ssh -G` against `SSHUB_SSH_CONFIG` / + `SSH_LAUNCHER_SSH_CONFIG` / `~/.ssh/config`; alias listing follows `Include` + directives depth-capped at 16. See [hosts & identities](../domain/hosts-identities.md). +- **`ssh-keygen`** — per-line fingerprinting for the known-hosts manager + (`ssh-keygen -l -f -` in `src/known_hosts.rs`), guarded deletion (`ssh-keygen -R`), + and key/passphrase probing (`ssh-keygen -y` in `src/ssh/keyfile.rs`) where the + passphrase is staged through a 0600/0700 `KeygenAskpass` helper rather than argv. + See the [known-hosts manager](../workflows/known-hosts.md) and + [secrets](../security/secrets.md). +- **`known_hosts`** — the SFTP transport verifies host keys trust-on-first-use + against `~/.ssh/known_hosts`, appends unknown entries *manually* (libssh2's file + writer would drop unparsable lines such as `@cert-authority`) and treats a changed + key as a hard MITM error (`src/sftp/transport.rs`). +- **Third-party host exports** (`src/import/`) — Termius backups (`L00t.csv` + + `ssh_keys/`, secrets re-stored with write-verification), PuTTY saved sessions (a + Windows `regedit /e` `.reg` export or a Unix `~/.putty/sessions` directory; SSH + only), and mRemoteNG `confCons.xml` (SSH-protocol connections; encrypted passwords + not decrypted). All three share `HostImportReport`. See + [hosts & identities](../domain/hosts-identities.md). +- **GitHub Actions / crates.io / npm registry** — the tag-triggered release workflow + builds the three platform tarballs, creates the GitHub release, repacks those same + tarballs into the npm packages (OIDC trusted publishing), and publishes the crate + with `CARGO_REGISTRY_TOKEN`. See [CI & automation](../operations/ci-cd.md) and + [build & release](../operations/build-release.md). + +## Change guidance + +- Legacy-key tolerance is pinned by `parse_config_applies_overrides` + (`src/config.rs` tests); anything new that removes a config key should extend that + test rather than adding runtime behavior. +- Install-channel and version-label rules are pinned by the `tab_bar.rs` unit tests + (`install_channel_resolution`, `version_label_gains_channel_suffix`, + `version_label_resolution`); the npm shim has no automated test — `npm/build.sh`'s + Linux verification step is its only end-to-end check. +- Demo recordings are their own regression test: the pacing verdict (`ok` vs `OFF`) + fails the run when a take drifts from the scripted timeline; keep scenario scripts + and expected durations in sync when changing animation timing. +- The fake `demo/bin/ssh` must keep matching the markers SSHub looks for (the + `Authenticated to` connected needle and the stderr-siphon side channel) whenever + the session lifecycle changes. diff --git a/openwiki/integrations/index.md b/openwiki/integrations/index.md index d2777b65..74ef5d4d 100644 --- a/openwiki/integrations/index.md +++ b/openwiki/integrations/index.md @@ -1,3 +1,3 @@ # Files -- [Integrations — terminal stream demos and external touchpoints](external-terminals.md) - SSHub's current integration surface includes embedded PTY demo recording, OS keyring, SSH tooling, and GitHub/crates.io automation; the former external-terminal launcher was removed in 0.10.0. +- [Integrations — external touchpoints and demo pipeline](external-terminals.md) - SSHub's current external surface — the removed external-terminal launcher (only ignored legacy config keys remain since 0.10.0), the npm shim and packaging pipeline (sshub-tui + platform optional dependencies, SSHUB_INSTALL_CHANNEL=npm), the terminal-stream demo recorder in demo/, and the external tools SSHub drives (OS keyring, ssh-agent, ssh -G, ssh-keygen, known_hosts, third-party host exports, GitHub Actions/crates.io). diff --git a/openwiki/operations/build-release.md b/openwiki/operations/build-release.md index 4cf93ac3..c3fe7d3c 100644 --- a/openwiki/operations/build-release.md +++ b/openwiki/operations/build-release.md @@ -1,9 +1,42 @@ --- type: Playbook title: Build, Versioning & Release — Justfile recipes, odometer versioning, and release flow -description: How to build, test, version, and release SSHub — Justfile recipes, the odometer vX.Y.Z versioning scheme with the pre-commit auto-bump hook, the feature/*→development→main branch model, and the one-command just release flow. +description: How to build, test, version, and release SSHub — everyday Justfile recipes, the vendored-OpenSSL and release-profile tradeoffs, the shared multi-agent Cargo target, odometer vX.Y.Z versioning with the pre-commit auto-bump hook, the just release / hotfix / sync flows, the npm distribution, and the feature/*→development→main branch model. resource: Justfile -tags: [build, release, versioning, just, operations] +tags: [build, release, versioning, just, npm, operations] +verified: + - by: openwiki/0.5.1 + at: 2026-09-11T12:46:43.817Z +sources: + - id: openwiki-source-bf5be0c9253ed1d07b502e10 + resource: repo://.githooks/pre-commit + - id: openwiki-source-164e2da859b5277df81c7d94 + resource: repo://.github/workflows/ci.yml + - id: openwiki-source-4d1d392666be6dfdd7a91a2e + resource: repo://.github/workflows/release.yml + - id: openwiki-source-8037e2358a2c4f9b2c722a11 + resource: repo://AGENTS.md + - id: openwiki-source-651d1fb6c9e49916a916ab51 + resource: repo://Cargo.toml + - id: openwiki-source-a2371d6362e5db4bc834ad03 + resource: repo://CLAUDE.md + - id: openwiki-source-f317ee207e1653d2033c81a4 + resource: repo://CONTRIBUTING.md + - id: openwiki-source-ce74741e295fb3f30c5fc1b9 + resource: repo://docs/coverage-map.md + - id: openwiki-source-14e6a4e1ddc06973b783ff20 + resource: repo://Justfile + - id: openwiki-source-894f7168bd5e9e5aa1dcd57a + resource: repo://npm/build.sh + - id: openwiki-source-29f2ffe501d0bdc557fcc14a + resource: repo://npm/wrapper/bin/sshub.js + - id: openwiki-source-fe697126247103d981f7863b + resource: repo://npm/wrapper/package.json.in + - id: openwiki-source-58f2837f702d3f604de2bc20 + resource: repo://npm/wrapper/README.md + - id: openwiki-source-c199774f9567428d199a4f6d + resource: repo://scripts/uncovered-functions.py +generated: { by: "openwiki/0.5.1", at: "2026-09-11T12:46:43.817Z" } --- # Build, Versioning & Release @@ -11,9 +44,10 @@ tags: [build, release, versioning, just, operations] ## Everyday commands ```bash -just build # cargo build --release -just test # unit + smoke + e2e + config_load (all four targets) +just build # cargo build --release (default features — the shipping configuration) +just test # unit + smoke + e2e + config_load (all four targets, --no-default-features) just dry-run # cargo run -- --dry-run +just coverage # cargo-llvm-cov + the never-executed function list (docs/coverage-map.md) ``` Lint gate (run before **every** push; [CI](ci-cd.md) runs the same): `cargo fmt`, `cargo fmt --check`, `cargo clippy --all-targets`. @@ -22,18 +56,57 @@ Lint gate (run before **every** push; [CI](ci-cd.md) runs the same): `cargo fmt` | Recipe | What it does | |---|---| -| `test` | All 4 test targets (see [testing](../testing/strategy.md)) | -| `build` | Release binary (prerequisite of `install`) | +| `test` | All 4 test targets with `--no-default-features` (see [testing](../testing/strategy.md)) | +| `build` | Release binary (prerequisite of `install`; default features — what ships) | +| `coverage` | cargo-llvm-cov + `scripts/uncovered-functions.py` → the never-executed list for `docs/coverage-map.md` | | `install` / `uninstall` | Binary → `~/.local/bin`, completions, man page, icon + `.desktop` entry (kitty→ghostty→alacritty→foot→xterm detection) | | `install-completions` | bash/fish completions to auto-load dirs; sourced zsh block appended to `~/.zshrc` | | `man` | Preview `man/sshub.1` via `man -l` | -| `bump [version]` | Odometer version bump in `Cargo.toml` + `Cargo.lock` (no cargo invocation) | -| `release [minor|patch|X.Y.Z]` | Full release: settle version on `development`, roll CHANGELOG, `--no-ff` merge to `main`, tag `vX.Y.Z`, push, ff `development` | +| `bump [version]` | Odometer version bump in `Cargo.toml` + `Cargo.lock` (no cargo invocation) | +| `release ` | Full release: settle version on `development`, roll CHANGELOG, `--no-ff` merge to `main`, tag `vX.Y.Z`, push, ff `development` | | `sync` | Merge `development`→`main` without a release (no tag/bump/changelog) | +| `npm-build [version]` | Assemble the npm packages + wrapper into `npm/dist` from the GitHub release tarballs (no compilation; shim exercised first) | +| `npm-publish [version]` | `npm-build` then publish: platform packages first, the `sshub-tui` wrapper last | | `setup-hooks` | One-time `git config core.hooksPath .githooks` per checkout | -| `record-gifs *tapes` | VHS + ffmpeg demo recording (see [integrations](../integrations/external-terminals.md#demo-pipeline)) | +| `setup-shared-target` | Point this checkout's build output at the shared `../.cargo-target` (writes untracked `.cargo/config.toml`) | +| `worktree-add [branch]` | Isolated worktree under `../.worktrees/` on `feature/` (default), sharing the cargo target | +| `worktree-rm [delete-branch]` | Remove the worktree; sweeps its stale artifacts | +| `sweep [size=4GB]` | Prune the shared target dir with cargo-sweep (by size; use `cargo sweep --time N` for by-age) | +| `record-gifs *scenarios` | PTY-capture demo recording via `demo/record.py` (needs `agg` + ffmpeg; see [integrations](../integrations/external-terminals.md)) | | `dry-run` | Headless sanity check | +## Vendored OpenSSL — the default feature that makes shipped builds self-contained + +The `vendored` feature (`ssh2/vendored-openssl`, compiling OpenSSL from source) is **on by default** because `cargo install sshub` and the release tarballs must build with **no system OpenSSL present**. It is a feature only so local development can opt **out** with `--no-default-features`, linking the system libs instead — about 120 MB less `target/` per profile and most of a cold build's wall clock. `just test` already passes the flag. + +Never pass `--no-default-features` on anything that ships: `just build`, CI (`cargo build --all-targets` / `cargo test`), and the release workflow all stay on the default, which is what makes the tarball, `cargo install sshub`, and crates.io builds self-contained. + +## Release profile: fast binary, no backtraces + +`[profile.release]` sets `lto = "thin"`, `codegen-units = 1`, and `strip = "symbols"`: + +- **Slow release builds, optimized binary.** Thin LTO plus a single codegen unit optimize across crate boundaries — this is the binary the tarballs, `cargo install sshub`, and npm all ship. +- **Empty `RUST_BACKTRACE` in shipped builds.** Release never carried debug info (release defaults to `debug = false`), and `strip = "symbols"` removes the symbol table — so a panic backtrace in anything shipped is empty. Reproducing a panic with a readable backtrace requires a **debug** build. The dev profile compensates for disk: full DWARF for every dependency was ~40% of `target/`, so dev uses `debug = "line-tables-only"` — line tables keep backtraces readable for SSHub's own code. +- **`panic` stays on `unwind`, on purpose.** Not for the tty (the panic hook restores it under `abort` too): with unwind, a worker thread's panic (watcher, ping, broadcast, sftp, os-detect) kills only that thread and leaves the TUI alive, and every cleanup `Drop` still runs — including `AskpassSecret`, which removes the file holding a host's password. + +## Multi-agent worktrees & the shared Cargo target + +Several agents work in parallel on one machine: the main checkout stays in `ssh-tui/`, extra agents get git worktrees, and **all** of them share one Cargo artifact dir to save disk (~1.2 GB per full dev+test+release cycle — not one such dir per agent): + +```text +sshub-dev/ + .cargo-target/ ← real cargo artifacts (build.target-dir) + .worktrees// ← isolated checkouts + ssh-tui/ ← main checkout + .cargo/config.toml ← untracked (gitignored), points at ../.cargo-target +``` + +- **`just setup-shared-target`** — run once per checkout/worktree. Writes an untracked `.cargo/config.toml` with an **absolute** `build.target-dir` (absolute because worktrees sit one level deeper than the main checkout, so no single relative path fits both). There is deliberately **no `target` symlink**: `cargo clean` deletes a symlink, after which cargo silently starts building into a private `./target` and the shared dir quietly becomes one copy per agent. If you find a `target` symlink, don't repair it by hand — `setup-shared-target` migrates it (and an existing real `./target`) into the shared dir. +- **`just worktree-add [branch]`** — creates `../.worktrees/` on branch `feature/` (default) and points its build at the shared target. Both worktree recipes resolve the main checkout via `git rev-parse --git-common-dir`; `git rev-parse --show-toplevel` is wrong here — run from a worktree it yields `.worktrees/` and you get nested `.worktrees/.worktrees/`. +- **`just worktree-rm [delete-branch]`** — force-removes the worktree; the branch survives unless you pass `delete-branch`. That branch's artifacts become unreachable but stay in the shared dir, so the recipe sweeps when cargo-sweep is installed. +- **`just sweep [size=4GB]`** — cargo **never** deletes artifacts of branches you moved off, so the shared dir only grows; `sweep` prunes it with `cargo sweep --maxsize`, capped at 4 GB by default. It must be invoked on the **project path** — cargo-sweep resolves the target dir itself, and pointed at the target dir it looks for a `Cargo.toml` inside it. +- **Never run two cargo builds (or two `just test`) at once against the shared target** — fingerprint races. Serialize: one agent compiling at a time. + ## Versioning — odometer `vX.Y.Z` Each field rolls 0–9 and carries (`0.9.9 + patch → 1.0.0`): @@ -44,16 +117,72 @@ Each field rolls 0–9 and carries (`0.9.9 + patch → 1.0.0`): `main` is not always `X.Y.0`: hotfix releases publish `development`'s current `X.Y.Z` unchanged. +`just bump [version]` edits `Cargo.toml` and the `sshub` entry in `Cargo.lock` with `sed` — no cargo invocation, keeping the pre-commit hook fast and offline. Carries: `0.4.9 + patch → 0.5.0`, `0.9.9 + patch → 0.10.0`, `0.99.0 + minor → 1.0.0`, `0.99.9 + patch → 1.0.0`. + +### The pre-commit auto-bump hook + +The hook is tracked in the repo but **not active on clone** (git doesn't share hooks) — `just setup-hooks` sets `core.hooksPath .githooks` once per checkout. On every commit it: + +1. checks the branch-name shape via `scripts/check-branch-name.sh` (the "some commit is a `fix:`" substantive check runs in CI, which sees the whole branch); +2. exits unless the branch is `development` — other branches never tick the counter; +3. exits while a merge/rebase/cherry-pick is in progress (`MERGE_HEAD`/`REBASE_HEAD`/`CHERRY_PICK_HEAD`), and no-ops if `just` isn't installed, so it can never block a contributor without the toolchain; +4. runs `just bump patch` and re-stages `Cargo.toml` + `Cargo.lock`. + +Contributors therefore never bump the version by hand (CONTRIBUTING.md says so explicitly). + ## Release flow (`just release`) -Run from a clean `development`: +Run from a clean `development`. The recipe **requires an explicit kind** — `minor`, `patch`, or `X.Y.Z`. There is no default on purpose: a bare `just release` used to mean `minor`, silently bumping when you meant to ship what `Cargo.toml` already says. + +```mermaid +flowchart TD + Start["just release with kind minor, patch or X.Y.Z"] --> Guard{"on clean development with a valid kind"} + Guard -->|"no"| Stop["refuse with usage"] + Guard -->|"yes"| Settle{"kind"} + Settle -->|"minor"| Minor["just bump minor - Y up, Z to 0"] + Settle -->|"patch"| Patch["keep the current X.Y.Z on development"] + Settle -->|"X.Y.Z"| Explicit["just bump set X.Y.Z"] + Minor --> Roll["roll CHANGELOG and commit chore: prep release vX.Y.Z with --no-verify"] + Patch --> Roll + Explicit --> Roll + Roll --> Tagged{"vX.Y.Z already tagged?"} + Tagged -->|"yes"| Restore["restore Cargo.toml and Cargo.lock, exit 1"] + Tagged -->|"no"| Merge["merge --no-ff development into main as chore: release vX.Y.Z"] + Merge --> Tag["tag -a vX.Y.Z and push --follow-tags"] + Tag --> FF["fast-forward development to the release merge"] + FF --> Next["next dev commit hook-bumps to X.Y.Z+1"] +``` + +*The `just release` flow: settle the version on `development`, roll the changelog, merge to `main`, tag (the tag triggers [the release workflow](ci-cd.md)), then converge the branches.* -1. **Settle on `development`** — set the release version in `Cargo.toml` + lock, roll the CHANGELOG (`[Unreleased]` → `[X.Y.Z] - `, fresh empty `[Unreleased]` on top), commit as `chore: prep release vX.Y.Z` with `--no-verify` (so the hook doesn't re-bump). -2. **Merge & tag** — `git merge --no-ff development` into `main` (`chore: release vX.Y.Z`), tag `vX.Y.Z`, push. The tag triggers the [release workflow](ci-cd.md) → binaries + crates.io. +1. **Settle on `development`** — set the release version in `Cargo.toml` + lock, roll the CHANGELOG (`[Unreleased]` → `[X.Y.Z] - `, fresh empty `[Unreleased]` on top), commit as `chore: prep release vX.Y.Z` with `--no-verify` (so the hook doesn't re-bump). Settling **on development** (not on main) is what keeps the dev odometer running from the released `X.Y.Z` instead of going stale — a stale dev version made the next `just release minor` collide with an existing tag. The recipe refuses an already-tagged `vX.Y.Z` (restores the files, exits 1), skips the changelog roll if the section already exists (a recovery re-run), and skips the prep commit if there is no diff. +2. **Merge & tag** — `git merge --no-ff development` into `main` (`chore: release vX.Y.Z`), tag `vX.Y.Z`, push. That one tag push is the single trigger for every distribution channel: the release workflow builds the three target tarballs, creates the GitHub release with `CHANGELOG.md` as the body, publishes to crates.io, and assembles + publishes npm from the same artifacts — nothing is published until it lands. 3. **Converge** — fast-forward `development` to the release merge so both branches point at the same commit; the next dev commit hook-bumps to `X.Y.Z+1`. -Consequences: `git log --first-parent main` shows one commit per release; reverting a whole release is `git revert -m 1 `; reverting one feature is reverting its squashed dev commit. `just release patch` ships whatever `development` currently holds — land only the fix first if `development` carries unreleased work. Pushing to protected `main` relies on the owner's admin bypass. +Consequences: `git log --first-parent main` shows one commit per release; reverting a whole release is `git revert -m 1 `; reverting one feature is reverting its squashed dev commit (after reverting a merge, re-landing the same history needs a revert of the revert). Merges stay clean because `development` is ff'd to `main` after every release, so `main` is always an ancestor of `development`; if `main` ever gets a direct commit anyway, merge `main` into `development` first. `just release patch` ships whatever `development` currently holds — land only the fix first if `development` carries unreleased work. Pushing to protected `main` relies on the owner's admin bypass. + +**`just sync`** brings `main` up to date with `development` between releases — a `--no-ff` `chore: sync development into main` merge with **no** version bump, changelog roll, or tag, so the release workflow is **not** triggered (docs/CI fixes ride the next release anyway; sync is for when they can't wait). `development` is fast-forwarded back, keeping the next release merge clean. + +### Hotfix flow (pointer) + +`just release` runs only on `development` and ships everything sitting there. To ship fixes while **holding back** work already on `development`, use the hotfix flow in [CLAUDE.md § Hotfix release](../../CLAUDE.md): branch `fix/release-X.Y.Z` from `origin/main`, cherry-pick the squashed per-PR commits, **scrub the excluded feature out of what ships** (its CHANGELOG entries stay behind, and any other entry that merely mentions it must lose that mention — grep `src/`, `man/`, `README.md`, completions, help text), set the version and roll the changelog by hand (`just bump set X.Y.Z`), verify the **release tree**, open a PR into `main` for CI only (don't merge with the button — `main`'s first-parent line must read one `chore: release vX.Y.Z` per release), merge and tag locally, then **merge `main` back into `development` right away** — skipping that last merge lets the next `development → main` merge quietly revert the fix. + +## npm distribution (`just npm-build` / `just npm-publish`) + +npm users get the **same prebuilt binaries the GitHub release ships** — `npm/build.sh` compiles nothing. + +- **`just npm-build [version]`** downloads the `sshub-*.tar.gz` artifacts attached to the `vX.Y.Z` GitHub release (`gh release download`; `TARBALL_DIR=dir` uses local copies instead, which is what CI passes), extracts each binary into a per-platform package — `sshub-linux-x64`, `sshub-darwin-arm64`, `sshub-darwin-x64` — with `package.json`/README rendered from `npm/platform/*.in` (including each package's `os`/`cpu` matchers), and assembles the `sshub-tui` **wrapper** around `npm/wrapper/bin/sshub.js`, whose `package.json.in` lists the three platform packages as pinned `optionalDependencies` (npm installs the one matching the machine and skips the rest). Before declaring success it verifies: on a Linux x64 host the extracted binary must report the packaged version, and the shim — driven exactly the way `npx` would, via a `node_modules` symlink mirroring a real install — must reach it and report the same. Every package is also `npm pack --dry-run`-ed. +- **`just npm-publish [version]`** is the same script with `--publish` (local runs need `npm login` or a token in `~/.npmrc`): it publishes the **three platform packages first and the wrapper last**, because the wrapper's `optionalDependencies` must exist by the time it lands — published the other way round, npm resolves the wrapper to no binary and installs fail for everyone in that window. Versions already in the registry are skipped rather than fatal, so re-running after a partial failure is idempotent. +- **The wrapper is `sshub-tui`, not `sshub`**: npm rejects the bare name as too close to the existing `ssh2` and `sshpk` packages. The command it installs is still `sshub` (the bin name is independent of the package name). +- The shim never downloads anything: it maps `process.platform-arch` to the one platform package that installed, `require.resolve`s its `bin/sshub`, repairs a missing executable bit, and spawns the binary with `stdio: 'inherit'` (the real tty the TUI needs, same process group so Ctrl+C reaches the child) and `SSHUB_INSTALL_CHANNEL=npm` — the env marker the version badge reads to report the npm install channel, because the prebuilt binary is byte-identical to the release-tarball one and path heuristics cannot tell them apart. The shim propagates the child's signal and exit status. +- In [CI](ci-cd.md), the release workflow's npm job runs `npm/build.sh "" --publish` with the workflow artifacts as `TARBALL_DIR` — the version comes from the **tag**, so a tag/version mismatch fails in CI instead of reaching the registry — and authenticates by trusted publishing (OIDC), not a long-lived token. + +## Coverage — where the tests never go + +`just coverage` runs `cargo-llvm-cov` twice (JSON export + summary) and pipes the JSON into `scripts/uncovered-functions.py`, which prints the `src/` functions **no test executes even once**, largest first — the tables in `docs/coverage-map.md`. It needs cargo-llvm-cov plus `llvm-tools-preview` on a rustup toolchain; on a distro toolchain (no rustup) the recipe points cargo-llvm-cov at the system `llvm-cov`/`llvm-profdata` itself. + +The percentage is the boring number; the never-executed list is the useful one — a function no test enters has never been observed working, no matter how many passing tests exist. That is why there is deliberately **no coverage floor in CI**: a percentage gate is satisfied by tests that execute code without asserting anything. The script handles the traps that produced confidently wrong numbers before: the report lists each function once per test binary (counts must be summed per mangled name), a zero count alone is not proof (degenerate entries on executed lines exist — line segments are the arbiter), and single-line spans are unused instantiations, not functions. ## Branch model & contributor flow -`feature/*` branches cut from `development` → PR → `development` → release merges to `main`. Merged branches are deleted (GitHub auto-delete enabled). The pinned pipeline (issue → claim → branch → verify → adversarial review → PR) is [docs/implementation-flow.md](../../docs/implementation-flow.md); agent-authored GitHub comments must end with `_Written by {Model} ({Platform}) on behalf of the maintainer._` See `CONTRIBUTING.md` and `CLAUDE.md` (canonical rules; do not duplicate them here). +`feature/*` branches cut from `development` → PR → `development` → release merges to `main`. `main` is releases only — never a PR target. Merged branches are deleted (GitHub auto-delete enabled; delete locally after a CLI merge). Branch names are `/` keyed to what the work *is* (`feature/` = `feat`, `fix/` = `fix`, `docs/` = `docs`, `chore/` = chore/ci/build/refactor/perf/style/test, `poc/` = anything), enforced by `scripts/check-branch-name.sh` in the pre-commit hook (shape only) and in CI (commit types of every commit on the branch; forks are skipped). The pinned pipeline (issue → claim → branch → verify → adversarial review → PR) is [docs/implementation-flow.md](../../docs/implementation-flow.md); agent-authored GitHub comments must end with `_Written by {Model} ({Platform}) on behalf of the maintainer._` See `CONTRIBUTING.md` and `CLAUDE.md` (canonical rules; do not duplicate them here). diff --git a/openwiki/operations/ci-cd.md b/openwiki/operations/ci-cd.md index 0df0c6ba..f7fba326 100644 --- a/openwiki/operations/ci-cd.md +++ b/openwiki/operations/ci-cd.md @@ -1,36 +1,186 @@ --- type: Reference title: CI & Automation — GitHub Actions workflows -description: SSHub's four GitHub Actions workflows — ci.yml (test matrix + fmt/clippy lint gate), release.yml (tag-triggered binaries, GitHub release, crates.io publish), openwiki-update.yml (scheduled OpenWiki documentation regeneration bot), and strix-pentest.yml (PR security scan). +description: SSHub's four GitHub Actions workflows — ci.yml (branch-naming gate, two-OS test matrix, strict fmt/clippy lint), release.yml (tag-triggered binaries with a --dry-run smoke test, GitHub release from the CHANGELOG, crates.io publish, npm publish via OIDC trusted publishing), openwiki-update.yml (daily wiki-regeneration bot), and strix-pentest.yml (manual Strix security scan). resource: .github/workflows/ci.yml -tags: [ci, github-actions, release, automation, operations] +tags: [ci, github-actions, release, npm, automation, operations] +verified: + - by: openwiki/0.5.1 + at: 2026-09-11T12:46:43.817Z +sources: + - id: openwiki-source-bf5be0c9253ed1d07b502e10 + resource: repo://.githooks/pre-commit + - id: openwiki-source-164e2da859b5277df81c7d94 + resource: repo://.github/workflows/ci.yml + - id: openwiki-source-6d4b4e707b8d60b6ccfa3425 + resource: repo://.github/workflows/openwiki-update.yml + - id: openwiki-source-4d1d392666be6dfdd7a91a2e + resource: repo://.github/workflows/release.yml + - id: openwiki-source-8d8ce0b1596636d72fedd153 + resource: repo://.github/workflows/strix-pentest.yml + - id: openwiki-source-8037e2358a2c4f9b2c722a11 + resource: repo://AGENTS.md + - id: openwiki-source-651d1fb6c9e49916a916ab51 + resource: repo://Cargo.toml + - id: openwiki-source-ca6cb4b1a14fd7969dfae3ec + resource: repo://CHANGELOG.md + - id: openwiki-source-14e6a4e1ddc06973b783ff20 + resource: repo://Justfile + - id: openwiki-source-894f7168bd5e9e5aa1dcd57a + resource: repo://npm/build.sh + - id: openwiki-source-162da775d99462a69e333e24 + resource: repo://scripts/check-branch-name.sh + - id: openwiki-source-b55a21a31ede1b56cd31a6a6 + resource: repo://src/main.rs +generated: { by: "openwiki/0.5.1", at: "2026-09-11T12:46:43.817Z" } --- # CI & Automation -## `ci.yml` — test + lint +Four workflows under `.github/workflows/` carry all repository automation: -Triggers: pushes to `main`/`master`/`development` and all PRs. +| Workflow | Trigger | What it does | +|---|---|---| +| `.github/workflows/ci.yml` | push to `main`/`master`/`development`, every PR | branch-naming gate, test matrix, strict lint | +| `.github/workflows/release.yml` | tag push `v*` | builds the three release binaries, GitHub release, crates.io + npm publish | +| `.github/workflows/openwiki-update.yml` | `workflow_dispatch` + daily cron | regenerates the OpenWiki wiki, opens a PR | +| `.github/workflows/strix-pentest.yml` | `workflow_dispatch` only | Strix security scan of the tree | -- **test** — matrix over ubuntu/macos: `cargo build --all-targets`, `cargo test`. Linux installs `libdbus-1-dev`/`pkg-config` for the keyring backend. -- **lint** — ubuntu: `cargo fmt --check` + `cargo clippy --all-targets`. Matches the local gate in [build & release](build-release.md). +## `ci.yml` — the PR gate -## `release.yml` — binaries + crates.io +```mermaid +flowchart TD + Trig["push to main, master, development or any pull request"] --> Branch + Trig --> Test + Trig --> Lint + Branch["branch job - same-repo PRs only"] --> SelfTest["check-branch-name.sh --self-test"] + SelfTest --> Derive["derive conventional-commit types from the PR's commits"] + Derive --> Enforce["check-branch-name.sh head-branch types"] + Test["test job - ubuntu and macos matrix"] --> BuildTest["cargo build --all-targets, cargo test"] + Lint["lint job"] --> Fmt["cargo fmt --check"] + Fmt --> Clippy["cargo clippy --all-targets -- -D warnings"] +``` -Trigger: tag push `v*` (created by `just release`). +*The `ci.yml` jobs: branch naming is enforced only for same-repo PRs, tests run on two OSes, lint fails on any warning.* -- **build** — three targets (linux-x64, macOS arm64, macOS x64) → tar.gz artifacts. -- **release** — GitHub release via `softprops/action-gh-release` with `CHANGELOG.md` as the body. -- **publish** — crates.io publish using the `CARGO_REGISTRY_TOKEN` secret. Only versions carried by `main` when the tag is pushed get published. +### `branch` — branch-naming enforcement + +Runs only when the PR head repo equals the base repository — **forks are skipped**, because a contributor's branch name is not ours to police. It needs `fetch-depth: 0` so the whole branch history is available. Two steps: + +1. `scripts/check-branch-name.sh --self-test` — "check the checker" by running the script's own case table. +2. Check the actual branch, deriving the conventional-commit type of **every commit the PR adds**: + +```bash +types=$(git log --format=%s "$BASE_SHA..$HEAD_SHA" \ + | sed -nE 's/^([a-z]+)(\([^)]*\))?!?:.*/\1/p' | sort -u | tr '\n' ' ') +scripts/check-branch-name.sh "$HEAD_REF" $types +``` + +If no commit carries a recognizable conventional type, no types are passed and the check degrades to shape-only. + +The rules live in `scripts/check-branch-name.sh` and mirror [AGENTS.md § Branch naming](../../AGENTS.md): + +- Exempt names git or a workflow owns: `main`, `master`, `development`, `HEAD`, `openwiki/update`. +- Shape: `/` with a known prefix. +- Substance (only when commit types are supplied): at least one commit type must justify the prefix — `feature/`←`feat`, `fix/`←`fix`, `docs/`←`docs`, `chore/`←`chore, ci, build, refactor, perf, style, test`, `poc/`←anything. Supporting commits of other types are fine alongside. + +The check runs in **two modes** by design: the tracked [pre-commit hook](build-release.md) calls the script with the branch name only — the justifying commit may not be written yet, so a local hook can only honestly check shape — while CI sees the whole branch and enforces substance. That substantive mode is what catches a pure bug fix riding a `feature/` branch, the incident (#95) that turned the AGENTS.md prose into a check. The `--self-test` case table runs in the same CI job, so the checker itself cannot rot silently. + +### `test` — two-OS matrix + +`fail-fast: false` over `ubuntu-latest` + `macos-latest`, on `dtolnay/rust-toolchain@stable` with `Swatinem/rust-cache`. Linux installs `libdbus-1-dev` + `pkg-config` — the keyring Secret Service backend needs libdbus to compile. Then `cargo build --all-targets` and `cargo test` **with default features**, so vendored OpenSSL stays on: anything that ships must build with no system OpenSSL present (see [build & release](build-release.md)). Local `just test` passes `--no-default-features` for speed; CI does not. + +### `lint` — fmt + warning-free clippy + +- `cargo fmt --check`. +- `cargo clippy --all-targets -- -D warnings` — the tree has been warning-free since #50, so **any warning now fails the build**. Without the flag the pile regrew: 42 warnings on the `e2e` target were enough noise that a real warning in new code went unread. The job installs libdbus unconditionally because `--all-targets` compiles everything. + +This is the same gate as the local pre-push ritual (`cargo fmt --check` + `cargo clippy --all-targets`) documented in [AGENTS.md](../../AGENTS.md) and [build & release](build-release.md). + +## `release.yml` — one tag, three channels + +```mermaid +flowchart TD + Tag["push tag v* (created by just release)"] --> Build["build job - 3-target release matrix"] + Build --> Leg{"matrix leg"} + Leg -->|"linux-x64 and darwin-arm64"| Smoke["sshub --dry-run liveness check"] + Leg -->|"darwin-x64 Intel"| Sign["file type check + codesign sign and verify"] + Smoke --> Pkg["tar czf sshub tag target tar.gz + artifact upload"] + Sign --> Pkg + Pkg --> Rel["release job - GitHub release, body from CHANGELOG.md"] + Pkg --> Npm["npm job - assemble and publish via OIDC trusted publishing"] + Pkg --> Pub["publish job - cargo publish to crates.io"] +``` + +*The release pipeline: one tag push fans out to three distribution channels, all gated on the build matrix finishing.* + +Trigger: push of a `v*` tag with top-level `contents: write`. The tag is created by `just release` (which settles the version on `development`, merges `development` → `main` with `--no-ff`, tags, and pushes with `--follow-tags`) — that one tag push is the single trigger for every distribution channel. The `release`, `npm`, and `publish` jobs all `needs: build`, so nothing is published until all three target binaries exist. + +### `build` — three targets, each validated + +`fail-fast: false` matrix: + +| os | target | +|---|---| +| `ubuntu-latest` | `x86_64-unknown-linux-gnu` | +| `macos-latest` | `aarch64-apple-darwin` | +| `macos-latest` | `x86_64-apple-darwin` | + +Each leg installs its target toolchain, builds `cargo build --release --target ` (Linux adds libdbus), then **validates its own binary** before packaging: + +- **Smoke test** (`x86_64-unknown-linux-gnu`, `aarch64-apple-darwin`): run `./target//release/sshub --dry-run`, which exits immediately without opening the TUI — a safe liveness check, since nothing else in the job ever runs the binary it ships, and `strip = "symbols"` is exactly the kind of setting that can produce one that does not start (on Apple targets it rewrites the Mach-O, which can invalidate the linker's ad-hoc signature). +- **Intel Mac** (`x86_64-apple-darwin`): skipped for execution — an arm64 runner cannot execute the x86_64 binary without Rosetta — and checked structurally instead: `file` must report a `Mach-O 64-bit executable x86_64`, then `codesign --sign - --force` followed by `codesign --verify --strict --verbose=2`. It is *signed* rather than only verified because the linker gives an ad-hoc signature to arm64 Mach-Os (macOS requires one) but not to cross-compiled x86_64 ones — verify alone could only ever report "code object is not signed at all", as it did for v0.11.0. Signing after the build also means `strip` cannot leave a half-valid signature behind. + +Packaging is `tar czf sshub--.tar.gz sshub`, uploaded as an artifact named `sshub-`. + +### `release` — GitHub release + +Downloads all build artifacts with `merge-multiple: true` and creates the GitHub release via `softprops/action-gh-release@v2`, attaching the `sshub-*.tar.gz` files with `body_path: CHANGELOG.md` and `generate_release_notes: false` — the changelog that `just release` rolled is the release body, so there are no duplicate auto-notes. + +### `npm` — OIDC trusted publishing + +Runs on `ubuntu-latest` with `id-token: write` + `contents: read`. **There is no long-lived `NPM_TOKEN`**: every publish authenticates with a short-lived token minted for this workflow, and npm attaches provenance on its own. npm is also retiring direct publishes from 2FA-bypass tokens (announced 2026-07-08, effective around January 2027), so a token-based job would have had to move regardless. + +Steps: Node 24 with the npmjs registry configured, then `npm install -g npm@latest` — **trusted publishing needs npm ≥ 11.5.1**, newer than what Node bundles. The build artifacts are downloaded into `npm-tarballs` (used as `TARBALL_DIR`), and the job runs: + +```bash +npm/build.sh "${GITHUB_REF_NAME#v}" --publish +``` + +The version comes from the **tag**, not `Cargo.toml`, so a tag/version mismatch fails in CI instead of reaching the registry. `npm/build.sh` repacks the very tarballs the GitHub release ships — what npm serves is byte-for-byte what the release serves — renders the three platform packages (`sshub-linux-x64`, `sshub-darwin-arm64`, `sshub-darwin-x64`) plus the `sshub-tui` wrapper (npm rejects the bare `sshub` name; the installed command is still `sshub`), and publishes **the platform packages first, the wrapper last**, because the wrapper's pinned `optionalDependencies` must exist when it lands. Versions already in the registry are skipped, so re-running a release job is idempotent rather than fatal. Each of the four packages needs a **trusted publisher configured on npmjs.com** pointing at this repository and `release.yml`, otherwise the step stops with `ENEEDAUTH`. (The npm distribution is covered in depth in [build & release](build-release.md).) + +### `publish` — crates.io + +Requires `build`, installs libdbus/pkg-config (the crate must compile to publish), and runs `cargo publish --token ${{ secrets.CARGO_REGISTRY_TOKEN }}`. That fails loudly if the tag's version was already published, so a re-run cannot silently no-op. Only versions carried by `main` when the tag is pushed get published — the tag sits on the release merge into `main`. ## `openwiki-update.yml` — wiki bot -Trigger: `workflow_dispatch` + daily cron `0 8 * * *`. Installs the `openwiki` npm CLI and runs `openwiki code --update --print` (OpenRouter provider, LangSmith tracing), then opens a PR from branch `openwiki/update` via `peter-evans/create-pull-request`. The PR path filter includes the workflow file itself, so bot config edits ride along with doc updates. +Trigger: `workflow_dispatch` + daily cron `0 8 * * *`, with `contents: write` + `pull-requests: write`. The job: + +1. Checks out the repo, sets up Node 22, and installs the `openwiki` npm CLI globally. +2. Runs `openwiki code --update --print ""`, where the prompt makes `CHANGELOG.md` the primary change ledger — audit every entry in `[Unreleased]` and every historical release regardless of the recorded OpenWiki gitHead, compare each against the source tree and existing wiki, add missing pages, and update indexes and cross-links — and maintains `openwiki/changelog-coverage.md` as the audit notebook (link every reviewed entry to pages, mark Covered/Pending, keep gitHead/date/model/unresolved list current). +3. Opens the update PR via `peter-evans/create-pull-request` (pinned to the v7 commit SHA): branch `openwiki/update` onto `development`, with `add-paths` limited to `openwiki`, `AGENTS.md`, and `CLAUDE.md` — the wiki output plus the two agent-rule files the bot maintains — and commit message/title `docs: update OpenWiki`. + +Environment: `OPENWIKI_PROVIDER=openrouter`, the `OPENROUTER_API_KEY` secret, model `z-ai/glm-5.3-flash`, and LangSmith tracing (`LANGCHAIN_PROJECT=openwiki`) that switches on only when `LANGSMITH_API_KEY` is non-empty. -## `strix-pentest.yml` — PR security scan +## `strix-pentest.yml` — manual security scan -Trigger: every pull request. Runs [Strix](https://strix.ai) in quick scan mode (`strix -n -t ./ --scan-mode quick`) against the checked-out tree. Auth: `STRIX_LLM` secret for the model name, and `LLM_API_KEY` mapped to the existing `OPENROUTER_API_KEY` secret (no extra key needed). +**Manual-only** (`workflow_dispatch`) — Strix scans consume substantial OpenRouter credits and may run for many minutes before returning findings, so they do not run on PRs. A `scan_mode` input defaults to `quick`. The job checks out with full history, installs [Strix](https://strix.ai) via `curl -sSL https://strix.ai/install | bash`, and runs: + +```bash +strix -n -t ./ --scan-mode "${{ inputs.scan_mode || 'quick' }}" +``` + +Auth is wired through env: `STRIX_LLM` holds the hardcoded model id `openai/gpt-5.6-luna` (a workflow value, not a secret), `LLM_API_KEY` is mapped to the existing `OPENROUTER_API_KEY` secret (no extra key needed), and `LLM_API_BASE` points at `https://openrouter.ai/api/v1`. ## Secrets used by workflows -`CARGO_REGISTRY_TOKEN` (crates.io publish), OpenRouter/LangSmith keys for the wiki bot, and `STRIX_LLM` for the PR pentest scan (referenced by env name only; see the workflow files for exact variable names). Never commit values. +The four workflows consume exactly three repository secrets: + +| Secret | Used by | Purpose | +|---|---|---| +| `CARGO_REGISTRY_TOKEN` | `release.yml` `publish` | crates.io publish token (passed as `cargo publish --token`) | +| `OPENROUTER_API_KEY` | `openwiki-update.yml`, `strix-pentest.yml` | OpenRouter provider key for the wiki bot; reused as Strix's `LLM_API_KEY` | +| `LANGSMITH_API_KEY` | `openwiki-update.yml` | LangSmith tracing (enables `LANGCHAIN_TRACING_V2` only when set) | + +npm publishing needs **no long-lived token at all** — it uses OIDC trusted publishing (per-package trusted-publisher entries on npmjs.com). `STRIX_LLM` is a workflow env value, not a secret. Never commit secret values. diff --git a/openwiki/operations/index.md b/openwiki/operations/index.md index d119a8a1..a621815a 100644 --- a/openwiki/operations/index.md +++ b/openwiki/operations/index.md @@ -1,4 +1,4 @@ # Files -- [Build, Versioning & Release — Justfile recipes, odometer versioning, and release flow](build-release.md) - How to build, test, version, and release SSHub — Justfile recipes, the odometer vX.Y.Z versioning scheme with the pre-commit auto-bump hook, the feature/*→development→main branch model, and the one-command just release flow. -- [CI & Automation — GitHub Actions workflows](ci-cd.md) - SSHub's four GitHub Actions workflows — ci.yml (test matrix + fmt/clippy lint gate), release.yml (tag-triggered binaries, GitHub release, crates.io publish), openwiki-update.yml (scheduled OpenWiki documentation regeneration bot), and strix-pentest.yml (PR security scan). +- [Build, Versioning & Release — Justfile recipes, odometer versioning, and release flow](build-release.md) - How to build, test, version, and release SSHub — everyday Justfile recipes, the vendored-OpenSSL and release-profile tradeoffs, the shared multi-agent Cargo target, odometer vX.Y.Z versioning with the pre-commit auto-bump hook, the just release / hotfix / sync flows, the npm distribution, and the feature/*→development→main branch model. +- [CI & Automation — GitHub Actions workflows](ci-cd.md) - SSHub's four GitHub Actions workflows — ci.yml (branch-naming gate, two-OS test matrix, strict fmt/clippy lint), release.yml (tag-triggered binaries with a --dry-run smoke test, GitHub release from the CHANGELOG, crates.io publish, npm publish via OIDC trusted publishing), openwiki-update.yml (daily wiki-regeneration bot), and strix-pentest.yml (manual Strix security scan). diff --git a/openwiki/security/index.md b/openwiki/security/index.md index 6bd0b79d..4bf4b15c 100644 --- a/openwiki/security/index.md +++ b/openwiki/security/index.md @@ -1,3 +1,3 @@ # Files -- [Secrets & Security — OS keyring, SSH_ASKPASS staging, host-key policy, and file permissions](secrets.md) - How SSHub handles secrets and security-sensitive behavior — OS keyring storage via the keyring crate, SSH_ASKPASS re-exec secret staging, TOFU host-key policies, session-log secret capture warning, and 0600/0700 permission enforcement. +- [Secrets & Security — OS keyring, SSH_ASKPASS staging, option-injection guards, host-key policy, and file permissions](secrets.md) - How SSHub handles secrets and security-sensitive behavior — OS keyring storage via the keyring crate with profile namespaces, SSH_ASKPASS re-exec secret staging, 0.14.2 option-injection guards on host fields, TOFU host-key policies, session-log secret capture warning, and 0600/0700 permission enforcement. diff --git a/openwiki/security/secrets.md b/openwiki/security/secrets.md index 71d1d6b1..17b974b3 100644 --- a/openwiki/security/secrets.md +++ b/openwiki/security/secrets.md @@ -1,9 +1,94 @@ --- type: Security Model -title: Secrets & Security — OS keyring, SSH_ASKPASS staging, host-key policy, and file permissions -description: How SSHub handles secrets and security-sensitive behavior — OS keyring storage via the keyring crate, SSH_ASKPASS re-exec secret staging, TOFU host-key policies, session-log secret capture warning, and 0600/0700 permission enforcement. +title: Secrets & Security — OS keyring, SSH_ASKPASS staging, option-injection guards, host-key policy, and file permissions +description: How SSHub handles secrets and security-sensitive behavior — OS keyring storage via the keyring crate with profile namespaces, SSH_ASKPASS re-exec secret staging, 0.14.2 option-injection guards on host fields, TOFU host-key policies, session-log secret capture warning, and 0600/0700 permission enforcement. resource: src/credentials.rs -tags: [security, secrets, keyring, askpass, permissions] +tags: [security, secrets, keyring, askpass, permissions, host-keys, injection] +verified: + - by: openwiki/0.5.1 + at: 2026-09-11T12:46:43.817Z +sources: + - id: openwiki-source-651d1fb6c9e49916a916ab51 + resource: repo://Cargo.toml + - id: openwiki-source-ca6cb4b1a14fd7969dfae3ec + resource: repo://CHANGELOG.md + - id: openwiki-source-ecf23cb964455b5721cf8ef2 + resource: repo://src/app/connect.rs + - id: openwiki-source-b03ae37b61b164e52acb0092 + resource: repo://src/app/host_form.rs + - id: openwiki-source-a65fcfa074dd6beeac4e84bb + resource: repo://src/app/import.rs + - id: openwiki-source-a43ecd76250acdab98621877 + resource: repo://src/app/mod.rs + - id: openwiki-source-c0d718e8edd8adec04ab3210 + resource: repo://src/app/push_key.rs + - id: openwiki-source-485a289ed462dcfba5216dee + resource: repo://src/app/session.rs + - id: openwiki-source-63b8e4964ec20ab415d5c35e + resource: repo://src/app/tests/host_form.rs + - id: openwiki-source-f531855e3a279281c944b8ad + resource: repo://src/app/util.rs + - id: openwiki-source-396ca803e3cf512685ab87bc + resource: repo://src/cli/exec.rs + - id: openwiki-source-16aa81e530496bda7ce97299 + resource: repo://src/cli/host.rs + - id: openwiki-source-066740c1fdbeed847cf52c24 + resource: repo://src/cli/inventory.rs + - id: openwiki-source-68d11374b04e1942d9c7448d + resource: repo://src/cli/output.rs + - id: openwiki-source-2a737474d86fc75cc9d9694f + resource: repo://src/config.rs + - id: openwiki-source-faa81a0468b230404a8b1a1f + resource: repo://src/credentials.rs + - id: openwiki-source-18c1e251293ee5d8bd127358 + resource: repo://src/import/mod.rs + - id: openwiki-source-6ba91c2b71cf890d1dc8d602 + resource: repo://src/import/mremoteng.rs + - id: openwiki-source-5cad0621d605640732d536f8 + resource: repo://src/import/putty.rs + - id: openwiki-source-8c30447f0ae8f6bc6153e0eb + resource: repo://src/import/termius_csv.rs + - id: openwiki-source-38dc2802701bc3b7c43855d1 + resource: repo://src/keybinds.rs + - id: openwiki-source-ed8bf05e307c6278442542c2 + resource: repo://src/lib.rs + - id: openwiki-source-b55a21a31ede1b56cd31a6a6 + resource: repo://src/main.rs + - id: openwiki-source-cba68790b7ceee059e92ec35 + resource: repo://src/osc52.rs + - id: openwiki-source-96306c574a085e3034e08698 + resource: repo://src/profile/migrate.rs + - id: openwiki-source-d7d76e3f61266c25b6860f64 + resource: repo://src/profile/mod.rs + - id: openwiki-source-2075792928906b50e8844b0e + resource: repo://src/secure_fs.rs + - id: openwiki-source-4699fa1d230faac97b2b376f + resource: repo://src/session_log.rs + - id: openwiki-source-39c7504511d4abe5494f1fca + resource: repo://src/session/askpass.rs + - id: openwiki-source-bf6cda76234f60dd7eecaa7f + resource: repo://src/session/mod.rs + - id: openwiki-source-6c3b297cfe77894e43250929 + resource: repo://src/session/parser.rs + - id: openwiki-source-ec2a6161f71baa54aa60a1c0 + resource: repo://src/sftp/transport.rs + - id: openwiki-source-1177e0e7ee3933b36ebabc4f + resource: repo://src/ssh/export.rs + - id: openwiki-source-8c90e650e97e8c2db8feaa52 + resource: repo://src/ssh/host.rs + - id: openwiki-source-5b3673a353d43a16cf57dfdb + resource: repo://src/ssh/import.rs + - id: openwiki-source-05c489e69ad06a37c8e614a1 + resource: repo://src/ssh/keyfile.rs + - id: openwiki-source-ef7bbf76631c2cc5e3fe2e35 + resource: repo://src/store/hosts.rs + - id: openwiki-source-5b73669f3eef941c822538ff + resource: repo://src/store/mod.rs + - id: openwiki-source-a18f10ec32b520311da809ec + resource: repo://src/tui/screens/help.rs + - id: openwiki-source-b3d2abf8ff74eda5980303d0 + resource: repo://src/tunnel/spawn.rs +generated: { by: "openwiki/0.5.1", at: "2026-09-11T12:46:43.817Z" } --- # Secrets & Security @@ -12,42 +97,90 @@ tags: [security, secrets, keyring, askpass, permissions] Host passwords and identity key passphrases live in the OS keyring through the `keyring` crate, service name `"sshub"`, keys `host:{id}` / `identity:{id}`. `PasswordStore` is the trait seam; `OsKeyring` in production, `NoopPasswordStore` in tests. SQLite stores only `has_password` flags — never secret material ([data model](../architecture/data-model.md)). +- **Profile namespaces:** `NamespacedPasswordStore` prefixes every key with `profile::` so identically named hosts in separate profiles never share a keyring entry; compatibility mode uses an empty prefix, so existing installs keep their current keys. - `Cargo.toml` enables real backends (`apple-native`, `windows-native`, `sync-secret-service`, `crypto-rust`): without a backend feature, keyring 3.x **silently falls back to an in-memory mock** that works within one process but persists nothing — which "looks exactly like 'passwords aren't being saved'". -- On Linux this needs the D-Bus Secret Service (build: `libdbus-1-dev`; runtime: an unlocked gnome-keyring/KWallet or similar). Without a provider, SSHub warns and ssh falls back to interactive prompting. -- [Termius import](../domain/hosts-identities.md#termius-import) re-stores imported secrets with write-verification and reports `keyring_failures` instead of dropping them silently. -- `sshub db purge` orphans keyring entries by design (only SQLite is wiped). -- If Secret Service is unavailable, SSHub uses an owner-only plaintext `credentials.json` in the data directory, written through a 0600 temporary file and atomic rename. The status reports the fallback; when a keyring becomes available, entries migrate back and the file is removed only after all entries succeed. -- Secret fields can be prefilled masked from the store. `Ctrl+R` reveals and copies, while `Ctrl+Y` copies without revealing; both actions report only that a value was copied. Leaving the field hides it again, and clearing a previously stored field explicitly deletes the credential. +- On Linux this needs the D-Bus Secret Service (build: `libdbus-1-dev`; runtime: an unlocked gnome-keyring/KWallet or similar). `check_keyring_available` probes the service and treats an absent `org.freedesktop.secrets` as unavailable; without a provider, SSHub warns and ssh falls back to interactive prompting. +- [Termius import](../domain/hosts-identities.md#importing-hosts) re-stores imported secrets with write-verification (`store_credential_verified` sets the value and reads it back) and reports `keyring_failures` instead of dropping them silently. +- `sshub db purge` orphans keyring entries by design (only SQLite is wiped — the output says "Passwords in the OS keyring are left untouched"). Deleting a whole *profile* is the opposite: it captures the profile's namespaced `host:{id}` / `identity:{id}` keys and removes them from the store, but only after the filesystem deletion commits, so a failed delete can restore the profile completely. + +### Plaintext fallback (`credentials.json`) + +If Secret Service is unavailable, SSHub uses an owner-only plaintext `credentials.json` in the data directory, written through a `0600` temporary file (symlink refused) and atomic rename. The status bar reports the fallback rather than letting the user assume the keyring took the secret. When a keyring becomes available again, `migrate_fallback_to_keyring` moves the entries back on the next launch and removes the file **only after every entry succeeded**. + +### Masked prefill, reveal/copy, cleared-field deletion + +Secret fields in the host and identity forms arrive prefilled from the store and masked: + +- `Ctrl+R` (`KeyAction::RevealSecret`) reveals and copies, `Ctrl+Y` (`KeyAction::CopySecret`) copies without revealing; both binds are rebindable and the form's hint row rides the password field while it is focused (`Ctrl+R: show + copy │ Ctrl+Y: copy`). Both actions report only that a value was copied — the notice names the secret ("password" / "passphrase"), never its value, and the value stays out of every log, audit row and diagnostic. +- Leaving the field re-masks it; the reveal bind is ignored away from the secret field. +- Saving compares the field against what the store held when the form opened (`password_original`), so a **cleared** field explicitly deletes the stored credential via `put_secret` — an empty field means "there is no secret any more", not "leave untouched". ## SSH_ASKPASS staging (`src/session/askpass.rs`) Secrets reach ssh without appearing in argv or PTY history: -1. The secret is written to a 0600 file under `$XDG_RUNTIME_DIR`. +1. The secret is written to a fresh `0600` file (`sshub-askpass-{pid}-{n}`) under `$XDG_RUNTIME_DIR` (or the system temp dir). 2. The child process gets `SSH_ASKPASS=`, `SSH_ASKPASS_REQUIRE=force`, `SSHUB_ASKPASS_FILE=`. 3. ssh re-executes sshub; `main.rs` calls `maybe_run_askpass()` **first** (before touching argv or the TUI), prints the staged secret, and exits. -4. The file is removed on Drop. Caveat: a SIGKILL can leave a stale staged file behind (no atexit cleanup) — see quickstart [backlog](../quickstart.md#backlog). +4. The file is removed on Drop. Caveat: cleanup is Drop-based, so a `SIGKILL` can leave a stale staged file behind. + +```mermaid +sequenceDiagram + participant App as SSHub + participant Store as Password store + participant Stage as 0600 staged file + participant SSH as ssh child + participant Reexec as sshub re-exec askpass + App->>Store: resolve_pending_secret + Store-->>App: PendingSecret + App->>Stage: AskpassSecret new writes secret + App->>SSH: spawn with SSH_ASKPASS and SSHUB_ASKPASS_FILE + SSH->>Reexec: exec helper at prompt + Reexec-->>SSH: prints staged secret and exits + SSH-->>App: authenticated session + App->>Stage: Drop removes file +``` + +*The askpass handoff: the secret travels through an owner-only file and a self re-exec, never argv, env, or PTY typing.* -The same mechanism feeds [tunnel](../workflows/tunnels.md) spawns (`stage_tunnel_askpass`) and `ssh-keygen -y` passphrase probing (`src/ssh/keyfile.rs`) — explicitly because "`ps` would expose it" in argv. The helper path is re-resolved when an in-place upgrade replaces the running executable, so password authentication does not require a restart; genuine helper lookup failures are written to the session log. +The same `helper_exe` + `AskpassSecret` mechanism feeds [tunnel](../workflows/tunnels.md) spawns (`stage_tunnel_askpass`), `sshub exec` and `sshub connect`, broadcast runs, and the OS auto-detect probe. `ssh-keygen -y` passphrase probing / generation (`src/ssh/keyfile.rs`) uses its own `KeygenAskpass` variant — a `0600` secret file plus a tiny `0700` helper script — explicitly because "`ps` would expose it" in argv. + +`helper_exe()` re-resolves the helper path when an in-place upgrade replaces the running executable: `/proc/self/exe` reports ` (deleted)`, which is stripped and retried, so password authentication does not require a restart after `cargo install` / `npm install -g`. A genuine helper lookup failure is written to the session log ("SSH_ASKPASS unavailable — will type at the prompt") instead of surfacing as a misleading server-side rejection. + +## Option-injection guards (0.14.2) + +A host field starting with `-` is parsed by ssh as a *flag*, not a value: an address of `-oProxyCommand=id` built `ssh … -oProxyCommand=id`, and ssh ran `id` **locally** (issue #101). Addresses are written verbatim from imported PuTTY, Termius and mRemoteNG files, so a crafted export was local code execution on the machine that imported it — no typo of the user's own required. Two layers defend: + +- **Connect-time backstop — `safe_ssh_target` (`src/ssh/host.rs`):** rewrites a leading-dash connection target into the `ssh://` URI form, which OpenSSH refuses outright instead of reading as an option; ordinary targets are untouched. Applied to every target sshub hands to ssh or mosh: managed/direct connects (`build_ssh_argv`), alias connects (`build_ssh_alias_argv`), mosh alias connects, and tunnel spawns (`build_tunnel_argv`). `ssh -- ` is not an alternative — the words after `--` become the remote command. A differential test drives the real `ssh -G`: the unguarded form still reports `proxycommand id`, the guarded one is refused outright. +- **Write-time refusal — `reject_option_like` / `is_option_like` (`src/store/hosts.rs`):** `create_host` and `update_host` refuse a leading-dash `name`, `address` or `username` with "starts with '-', which ssh reads as an option rather than a host — refusing to store it", so the poisoned row never lands and no later code path has to remember to defend. `is_option_like` is re-exported so the TUI host form can say so with the form still open ("{field} cannot start with '-' — ssh would read it as an option, not a host") instead of unwinding a store error through a key handler; `host add` goes through the same store guard. + +**Importers drop only the poisoned entry:** a refused `create_host` is counted per-row (`skipped_invalid`), one diagnostic names the host, and the rest of the file still imports. The CLI summary prints `N skipped (refused: a field ssh would read as an option)` alongside the normal counts. ## Clipboard relay boundary -[Embedded sessions](../workflows/sessions-sftp.md) relay OSC 52 clipboard writes only from the session currently visible. Empty payloads, clipboard reads, background tabs, and non-session views are refused or dropped; payloads and queue capacity are bounded. This is enabled by default but can be disabled with `[clipboard] relay_from_pty = false`. Because the sequence is in the raw PTY stream, enabled session logs may contain it. +[Embedded sessions](../workflows/session-terminal.md) relay OSC 52 clipboard writes only from the session currently visible, and only while the feature is on. Empty payloads, clipboard reads, background tabs, and non-session views are refused or dropped; payloads and queue capacity are bounded, and the selector is normalized to the host clipboard. The full boundary — visibility gate, caps, drop counters, read refusal, selector normalization — is documented on the [session-terminal page](../workflows/session-terminal.md) rather than restated here. + +What matters for this page: the feature defaults to on and can be disabled with `[clipboard] relay_from_pty = false`; a remote can never *read* the local clipboard; and because the sequence travels in the raw PTY stream, enabled session logs may contain it. The relay itself never puts a payload into any notice, diagnostic or log. ## Host-key policy -TOFU mirroring `accept-new` everywhere: ssh spawns inject `StrictHostKeyChecking=accept-new` when a secret is staged; [SFTP](../workflows/sessions-sftp.md#sftp) appends unknown keys to `~/.ssh/known_hosts` manually (libssh2's writer would drop unparsable lines) and treats a **changed** key as a hard MITM error. +TOFU mirroring `accept-new` everywhere: ssh spawns inject `-o StrictHostKeyChecking=accept-new` **when a secret is staged** — embedded sessions and `sshub connect` (`prepare_session_connect_argv` / `prepare_cli_connect_argv`), tunnel spawns (together with `BatchMode=no`), the inner `ssh` of a mosh argv, and public-key push. The rationale is deadlock avoidance: with `SSH_ASKPASS_REQUIRE=force`, an unanswered host-key confirmation would send the fingerprint question to the askpass helper and get the password back instead of "yes". Changed keys are still refused. On the session side, a changed key is detected in the `-v` log and the user can opt in to purging the stale entry (spec computed as `[addr]:port` for non-default ports, plain `addr` otherwise). + + +[SFTP](../workflows/sessions-sftp.md#sftp) mirrors the policy over libssh2: an unknown key is recorded and accepted — appended to `~/.ssh/known_hosts` **manually**, because libssh2's writer rewrites the whole file and would drop unparsable lines (`@cert-authority`, `@revoked`, certificates) — while a **changed** key is a hard "possible MITM" error. ## Session logs capture secrets -[Session logging](../workflows/sessions-sftp.md#session-logging) is opt-in and captures **everything echoed to the terminal — including typed passwords**. The in-app help screen carries this warning; keep it in sync if logging behavior changes. + +[Session logging](../workflows/sessions-sftp.md#session-logging) is opt-in and captures **everything echoed to the terminal — including typed passwords**. The in-app help screen carries this warning ("captures all PTY output including secrets echoed on screen"); keep it in sync if logging behavior changes. The same raw-stream caveat is why the clipboard relay section above points at session logs. ## Filesystem permissions (`src/secure_fs.rs`) -Best-effort (Unix-only) hardening: data/log/PID directories 0700, secret-bearing files 0600. Applied to session logs, askpass staging, tunnel PID files, and askpass helper scripts. +Best-effort (Unix-only) hardening — `restrict_dir` sets `0700`, `restrict_file` sets `0600`, and a chmod failure never fails the caller. Applied to data/log/PID directories and secret-bearing files: the launcher and metadata databases (plus their directories, so SQLite sidecars stay owner-only), `credentials.json`, session logs, askpass staging files, tunnel PID files, completion caches, exported config directories, and profile migration staging. Non-Unix platforms compile to no-ops. ## Input-safety details worth preserving - `src/ssh/export.rs::conf_val` flattens CR/LF in host fields so an exported `exported.conf` can't be used to inject a `Host *` stanza. -- Imports print nothing to stderr while the TUI is in raw mode (`src/ssh/import.rs`) — a diagnostic would corrupt the UI. -- the `resolve` CLI subcommand (`cmd_resolve` in `src/cli/host.rs`, JSON shape `HostResolveJson` in `src/cli/output.rs`) exposes only `has_stored_secret`, never the secret. +- Imports print nothing to stderr while the TUI is in raw mode (`src/ssh/import.rs`) — a diagnostic would corrupt the UI; failures are counted in the report instead. +- The `resolve` CLI subcommand (`cmd_resolve` in `src/cli/host.rs`, JSON shape `HostResolveJson` in `src/cli/output.rs`) exposes only `has_stored_secret`, never the secret; host records carry only the `has_password` flag. +- `sshub exec` writes an audit event with `via = exec` but **never records the command string** — it can carry a secret in an argument, and the audit log is not the place to keep one. (`--format json` echoes the command to the caller in its own `ExecRecord`; the boundary here is the shared audit table.) diff --git a/openwiki/workflows/broadcast.md b/openwiki/workflows/broadcast.md new file mode 100644 index 00000000..50ec4dea --- /dev/null +++ b/openwiki/workflows/broadcast.md @@ -0,0 +1,360 @@ +--- +type: Workflow +title: Broadcast Mode — Run One Command Across a Fleet +description: Broadcast mode runs one command across a host group or tag concurrently — the three-stage wizard with its dry-run barrier, the pure fan-out engine in src/broadcast with a bounded worker pool and BatchMode/SSH_ASKPASS auth, the docked live panel with focus/zoom, auto-dismiss and error toasts, and the one-audit-row-per-host contract. +resource: src/broadcast/mod.rs +tags: [broadcast, fleet, concurrency, workers, tui, audit, workflow] +verified: + - by: openwiki/0.5.1 + at: 2026-09-11T12:46:43.817Z +sources: + - id: openwiki-source-93bf2f07a39425fb927e9324 + resource: repo://src/app/broadcast.rs + - id: openwiki-source-e8c0db94ed2adcf662c346a3 + resource: repo://src/app/keys.rs + - id: openwiki-source-7fb1446f78fe2a70f1ff42cc + resource: repo://src/app/tests/broadcast.rs + - id: openwiki-source-7da0877d78263557d03ac935 + resource: repo://src/app/types.rs + - id: openwiki-source-f531855e3a279281c944b8ad + resource: repo://src/app/util.rs + - id: openwiki-source-9a4e39a3abc710e513d7a690 + resource: repo://src/broadcast/mod.rs + - id: openwiki-source-38dc2802701bc3b7c43855d1 + resource: repo://src/keybinds.rs + - id: openwiki-source-ed8bf05e307c6278442542c2 + resource: repo://src/lib.rs + - id: openwiki-source-39c7504511d4abe5494f1fca + resource: repo://src/session/askpass.rs + - id: openwiki-source-ef7bbf76631c2cc5e3fe2e35 + resource: repo://src/store/hosts.rs + - id: openwiki-source-344d0ae26e653accaaef1142 + resource: repo://src/tui/mod.rs + - id: openwiki-source-2560843f2b6e1f5c75ea5682 + resource: repo://src/tui/screens/broadcast.rs + - id: openwiki-source-f7eb9341184c727579092663 + resource: repo://tests/e2e/broadcast.rs +generated: { by: "openwiki/0.5.1", at: "2026-09-11T12:46:43.817Z" } +--- + +# Broadcast Mode + +Broadcast mode turns fleet chores (check disk, restart an agent, read a version) +into one action: pick a group or tag, type one command, and run it +non-interactively on every target host concurrently, then read the aggregated +per-host result. `b` on the hosts tab opens the wizard; the run itself is a +**background job**, not a modal — once confirmed, the mode returns to `Normal` +and the user can switch tabs while it executes, watching progress in a docked +panel that floats over the dashboard. Like the rest of SSHub there is no async +runtime: the fan-out rides the crate's thread + `mpsc` worker shape +([background workers](../architecture/overview.md#background-workers)). The +feature is split into a **pure engine** (`src/broadcast/`) and **app wiring** +(`src/app/broadcast.rs`), with the render layer in +`src/tui/screens/broadcast.rs`. + +```mermaid +flowchart TD + B["b on the hosts tab"] --> Menu["open_broadcast builds the target menu"] + Menu --> Pick["BroadcastPickTarget: pick a group or tag"] + Pick --> Cmd["BroadcastCommand: type the command"] + Cmd --> Prev["BroadcastPreview: dry-run barrier"] + Prev -- "y" --> Start["start_broadcast: tasks, pool, entry slide"] + Prev -- "e" --> Edit["edit-targets: Space toggles inclusion"] + Edit --> Prev + Prev -- "c" --> Cmd + Prev -- "N or Esc" --> Close["close the wizard"] + Start --> Pool["K = min(concurrency, tasks) workers"] + Pool --> Run["SshCommandRunner spawns ssh per host"] + Run --> Ev["BroadcastEvent on one mpsc Sender"] + Ev --> Tick["tick_broadcast folds events into rows"] + Tick --> Term{"all rows terminal?"} + Term -- "no" --> Pool + Term -- "yes" --> Audit["one auth_events row per host"] + Audit --> Settle["Settling: 6.5 s countdown"] + Settle --> Leave["exit slide, then the panel is removed"] +``` + +*A broadcast run end to end: the wizard stages feed `start_broadcast`, which spawns the pure pool; the poll loop folds worker events until every row is terminal, then audits, counts down, and dismisses the panel.* + +## The three-stage wizard (`src/app/broadcast.rs`) + +`open_broadcast()` refuses to run while a previous run is still live (any +non-terminal row → the notice "A broadcast run is already in progress."); a +**finished** panel still on screen is not yanked — it is sent off with the exit +slide while the wizard opens over it. One broadcast runs at a time by design. +The target menu is built from the real user-group list (the reserved Favorites +group is deliberately excluded) plus the sorted, deduplicated set of host tags; +with neither, the wizard refuses with a notice. + +The wizard rides three `AppMode` variants (`src/app/types.rs`): +`BroadcastPickTarget` → `BroadcastCommand` → `BroadcastPreview`. Each stage has +its own key handler in `src/app/broadcast.rs` and centered-popup renderer in +`src/tui/screens/broadcast.rs`: + +1. **Pick target** — ↑/↓ move the highlight, `Enter` resolves the highlighted + target and advances, `Esc` closes the wizard. Targets render as + `group: ` / `#`. +2. **Command** — a single-line text input (char / backspace / cursor keys via + `crate::text_input`, mirroring the SFTP prompt idiom). `Enter` advances only + on a non-empty command; `Esc` steps back to the target picker. +3. **Preview barrier** — a dry-run summary ("Run `` on N hosts + ():") with the full candidate list and the hint + `[y] confirm [e] edit targets [c] edit command [N] cancel`. + `y` starts the run; `e` enters edit-targets mode (↑/↓ move the row cursor, + `Space` toggles a host's inclusion, `Enter`/`e`/`Esc` return to the barrier); + `c` returns to the command prompt with the cursor parked at the end and + targets kept; `n`/`N`/`Esc` closes the wizard. The barrier protects against + an accidental fleet-wide `reboot`/`rm`. + +### Target resolution and credentials at pick time + +`resolve_broadcast_candidates` maps the picked target to **managed hosts only** +(entries without a managed id are excluded), kept in host-list order, each +selected by default. Per candidate it captures `ssh_argv_for_entry(entry)` +(`argv[0] == "ssh"`, alias-only for ssh_config-sourced rows, full options for +launcher rows) and resolves the stored credential **now** — +`resolve_pending_secret` (`src/app/util.rs`), the same path +[connect](../domain/hosts-identities.md), detect, tunnels and the CLI use — so +password hosts authenticate via askpass instead of failing under `BatchMode`. + +## The pure fan-out engine (`src/broadcast/mod.rs`) + +The engine module is **pure** — no `App`, no TUI dependency — so it is testable +in isolation like `osinfo::detect`. It owns the shared value types +(`BroadcastTask`, `HostState`, `HostResult`, `BroadcastEvent`), the +`CommandRunner` transport seam, the real `SshCommandRunner`, the bounded pool +(`spawn_broadcast`), and the reducer/view helpers the UI drives. + +- `BroadcastTask` — one host: `host_id`, `host_name`, `argv` + (`ssh_argv_for_entry`), and `secret: Option`. +- `HostState` — `Pending | Running | Done { exit } | Failed { reason }`; + `Done`/`Failed` are terminal. +- `HostResult` — one row of the aggregated table: identity, state, and the + captured `stdout`/`stderr` (ephemeral — see [audit](#audit-contract)). +- `BroadcastEvent` — `Started { host_id }` (optional/leading) plus exactly one + terminal `Finished { host_id, exit, stdout, stderr }` or + `Failed { host_id, reason }` per task. +- `CommandRunner::run(argv, command, secret, cancel)` returns + `Ok((exit, stdout, stderr))` for any completed exit code and + `Err(reason)` for spawn failure, timeout, or cancel — the seam tests inject + canned output through instead of spawning ssh. + +### Bounded worker pool (`spawn_broadcast`) + +`K = min(concurrency, tasks)` worker threads (clamped to at least one; the +app always passes `DEFAULT_CONCURRENCY = 8`) pull `BroadcastTask`s off a shared +`Arc>>` and emit events on a single +`Sender`. The task channel is preloaded and its sender dropped, +so a worker's `recv()` never blocks — it returns a task or ends the thread once +the queue drains. With zero tasks the pool returns an already-exhausted +receiver and spawns nothing. + +**Guarantee: exactly one terminal event per task**, including under cancel. +A worker that observes the cancel flag drains the rest of the queue marking +each remaining task `Failed { reason: "cancelled" }` without running it; +in-flight children are killed by the runner and surface as `Failed` too. A +runner that always errors cannot bring the pool down — every row simply ends +`Failed`. The senders are dropped when the workers exit, so the UI's drain loop +sees channel closure as "the run is over". + +### Transport: `SshCommandRunner` + +The real runner shells out to `ssh` with the same non-interactive option splice +as the OS-detect probe's `SshProbeRunner`: `-o BatchMode=…`, +`-o ConnectTimeout=N`, and `-o StrictHostKeyChecking=accept-new` right after +`argv[0]`, with the remote command appended as the final argv — inheriting +`~/.ssh/config`, ProxyJump, and the agent. There is **no PTY**. `ConnectTimeout` +(the constant is `CONNECT_TIMEOUT_SECS = 8`) bounds only the TCP connect; the +run itself is capped by `RUN_TIMEOUT = 60 s` and honored cancel via a +`try_wait` poll loop that wakes every 50 ms (`POLL_INTERVAL`) and kills the +child on cancel or timeout. stdout/stderr are drained on their own threads so a +chatty host cannot deadlock the other pipe; killing the child closes the pipes +and the readers join afterwards. Any completed exit code is a `Finished` +payload — a non-zero exit is a *failure* only in the view/audit layer +(below), not an `Err`. + +### Auth: `BatchMode` vs. a stored secret + +The split is per task, resolved when the target was picked: + +- **No stored secret** (`secret: None`): `BatchMode=yes` — key/agent auth only, + failing fast instead of blocking on a `/dev/tty` password prompt the + non-interactive spawn could never answer. +- **Stored credential** (`secret: Some`): `BatchMode=no`, and the runner stages + the secret through the exact `SSH_ASKPASS` path a live session uses — + `session::askpass::helper_exe()` plus an `AskpassSecret` guard (a `0600` + secret file removed on drop; env `SSH_ASKPASS` / `SSH_ASKPASS_REQUIRE=force` / + `SSHUB_ASKPASS_FILE`) — see [secrets](../security/secrets.md). The guard is + held for the whole poll loop so the file outlives the child. A unit test + pins that only hosts carrying a secret hand one to the runner. + +### Reducer and view helpers + +`apply_event` is the pure `BroadcastEvent → rows` fold: it mutates the matching +row (by `host_id`) in place, leaving length and order untouched — `Started` → +`Running`, `Finished` → `Done { exit }` + captured output, `Failed` → +`Failed { reason }`. A stray late `Started` never resurrects a terminal row, +and unknown host ids are ignored. `all_terminal` / `done_count` drive the +settle countdown and the `N/total` header badge. + +The **failures-first ordering is a render-time view, not a mutation**: +`failures_first` returns indices sorted by `state_rank` with a stable sort +(`Failed` and non-zero-exit `Done` rank 0, `Running` 1, `Pending` 2, clean exit +3; ties keep input order). A non-zero exit code counts as a failure — the +remote command ran but reported an error — which is also what `is_failure`, +`failure_count`, and `error_text` (the ssh failure reason, or the first +non-empty stderr line for a bad exit) feed. + +## The docked live panel (`src/tui/screens/broadcast.rs`) + +On `[y]`, `start_broadcast` builds tasks from the selected candidates, spawns +the pool with `SshCommandRunner::new()`, seeds one `Pending` row per task in +task order (`seed_results`), and arms the entry animation: a `SlideAnim` from +`spawn_rect` (centered over the dashboard body) to `docked_rect` (the +bottom-right corner) over `ENTRY_ANIM = 600 ms` with an ease-out curve +(`src/tui/tween.rs`). Under reduced motion the panel simply sits at its docked +rect. The panel deliberately starts **unfocused** — focusing would immediately +pause the completion countdown (focus means "the user is reading it"), so it +would never auto-dismiss. + +The panel is registered in the issue #18 focus ring as `PanelId::Broadcast`: +`Alt`+arrows reach it from the ping panel (right) and the ssh-log strip +(right), and hop back left/up; `focus_panel` skips hops onto it while no run is +live. The footer advertises `x cancel` while a run is live and `z zoom` once +focused. `z`/`Alt+Enter` zooms it full-body — the zoomed view shows a +failures-first host list (~55% height, selected row reverse-highlighted, scrolled +through the shared `zoom_window` scaffolding) above the selected host's +`stdout:`/`stderr:` detail panes. Unlike other panels, the zoom morph animation +is skipped for Broadcast (it has its own slide path), and the hosts-grid zoom +dispatcher deliberately leaves the zoomed Broadcast to `render_inner`'s own +block because it has no home in the bento grid. + +The docked panel draws the header `cast: · ` with a +`N/total` completion badge (plus `✗` when anything failed), per-host +rows in `failures_first` order (glyph, host name, status label, and a dim +failure-detail column), an overflow `…` marker when hosts exceed rows, and a +thin countdown gauge along the bottom while settling. + +### Panel lifecycle (`BroadcastPhase`) + +```mermaid +stateDiagram-v2 + [*] --> Running: preview confirmed + Running --> Settling: every row terminal + Settling --> Paused: panel focused + Paused --> Settling: focus leaves, countdown re-armed + Settling --> Leaving: 6.5 s countdown elapsed + Leaving --> [*]: exit slide finished, panel removed +``` + +*The docked panel's phases in `BroadcastPhase`; the per-host rows fold through `HostState` independently of this panel lifecycle.* + +`Running` ends when `all_terminal` holds, which arms `Settling { done_at }` — +the auto-dismiss countdown (`DISMISS = 6.5 s`, drawn as a depleting bar with a +`dismiss Ns` label). **Focusing the panel pauses the countdown** (`Settling` → +`Paused`); unfocusing re-arms it with a fresh timestamp. When the countdown +elapses, `slide_broadcast_out` plays the exit slide (dock → fully off the right +edge) into phase `Leaving`, and `tick_broadcast` removes the panel once the +slide ends — stamping `broadcast_panel_gone_at` so lingering toasts animate +*down* into the freed space instead of jumping. Opening the wizard over a +finished panel reuses the same exit slide. + +### Cancel and error toasts + +`x` (`KeyAction::BroadcastCancel`, default binding `x` in `src/keybinds.rs`) is +claimed before any other binding in `handle_key_normal` and works regardless of +focus: with a live run it calls `cancel_broadcast`, which sets the shared +`AtomicBool` — workers kill in-flight children and mark the rest +`Failed{"cancelled"}`, which the next tick folds in; with no run but lingering +toasts it clears the toasts. `open_broadcast`'s one-run-at-a-time refusal also +surfaces as a notice toast. + +Every failed host spawns an error toast (`BroadcastToast`): for a non-zero exit +the trimmed full stderr (or `exit N` when stderr is empty), for an ssh-level +failure the runner's reason. Toasts slide in from the right above the docked +panel, hold for `TOAST_TTL = 10 s`, then slide out over `TOAST_ANIM = 300 ms`; +the boxes size to their wrapped text (capped at 6 lines) and wear the same +error color as the row that produced them. They can outlive the panel: expiry +runs in every tick even with no broadcast, and once the panel is dismissed the +stack re-anchors into the vacated bottom-right, animating down over +`TOAST_ANIM` from `broadcast_panel_gone_at`. + +## Poll-loop integration + +`tick_broadcast` is called once per event-loop frame +(`src/lib.rs`, after the other worker drains — see +[runtime architecture](../architecture/overview.md)). Each tick it: + +1. expires finished toasts (before the `broadcast.is_none()` early return, so + toasts outliving the panel still animate out); +2. drains `BroadcastState.rx` with `try_recv`, folding each event through + `apply_event` and spawning a toast per failure; +3. retires the entry slide once done and arms `Settling` on the first tick + every row is terminal; +4. writes the audit trail (exactly once, below); +5. drives the `Settling`/`Paused`/`Leaving` countdown and dismissal. + +While any broadcast-driven motion is in flight — the entry slide, a toast's +slide-in/out, the post-dismissal stack drop, or a zoom morph — +`App::animating` reports true and `poll_keys_and_watcher` shortens its poll +window from 50 ms to 16 ms, so the ~600 ms entry slide renders at ~60 fps +instead of stepping at the idle 20 fps cadence. + +## Audit contract + +At completion — the first tick `all_terminal` holds, guarded by +`BroadcastState.audit_written` so a second tick can never duplicate rows — the +run writes **one `auth_events` row per host** through the same +`store.log_auth_event` the connect/session paths use, with +`via = "broadcast"` and no username. Broadcast is audited exactly like +connects, in the canonical status vocabulary so the rows integrate with the +audit tab's Ok/Fail filters, the stats query, and `theme::status_color`: + +- clean exit → status `launched`, note ` (exit N)`; +- non-zero exit → status `fail`, note ` (exit N: )` + via the shared `error_text` helper; +- ssh-level failure → status `fail`, note ` ()` — including + `"cancelled"` when the user aborts. + +Full stdout/stderr is deliberately **ephemeral** in v1 — captured live in +`HostResult`, visible in the panel and zoomed view, but not persisted; the +audit row carries only the one-line note. + +## Tests + +- **Engine invariants** (`src/broadcast/mod.rs` `#[cfg(test)]`, fake + `CommandRunner`): bounded concurrency (`peak ≤ K`), workers clamped to the + task count, empty task list yields no events, exactly one terminal event per + task, cancel-before-start marks everything terminal `Failed{"cancelled"}`, + an always-failing runner never panics the pool, reducer transitions preserve + row order and never resurrect terminal rows, stable failures-first ordering, + `failure_count` includes non-zero exits, and the real runner reports spawn + failure / short-circuits on pre-cancel / receives the per-task secret. +- **App wiring** (`src/app/tests/broadcast.rs`): the wizard group/tag flow + (pick → command → preview → `e`/`Space` deselect → `n` closes), the wizard + refuses to open over a live run, `tick_broadcast` folds events, arms + `Settling`, and writes the audit rows exactly once with the right + status/note, `cancel_broadcast` flips the shared flag, and the live panel + renders through `tui::render` wearing the theme's + `components.broadcast.error` role for a failed host. +- **E2E** (`tests/e2e/broadcast.rs`): drives the wizard through the real key + handlers into a rendered `TestBackend` buffer, asserting the "Broadcast to" + menu, the echoed command, the resolved host list, and the `[y]`/`[e]`/`[N]` + barrier — and deliberately **stops before `y`**, which would spawn real ssh; + everything up to the barrier is pure UI. +- **Render layer** (`src/tui/screens/broadcast.rs` tests): the docked panel and + zoomed view wear the `broadcast.panel` role bundle in both focus states, plus + countdown/toast/row-color coverage. + +## Change guidance + +- New pool behavior belongs in `src/broadcast/mod.rs` behind `CommandRunner`; + keep the module free of `App`/TUI deps and pin new invariants with fake + runners, not real ssh. +- The one-terminal-event-per-task guarantee and the audit-once guard are the + two invariants easiest to break silently — both have dedicated tests; keep + them green when touching `spawn_broadcast`, `apply_event`, or + `tick_broadcast`. +- The wizard is three `AppMode` variants + three key handlers + three render + functions; stage transitions must keep `broadcast_setup` and the mode in + sync (the e2e test pins the whole walk). diff --git a/openwiki/workflows/cli.md b/openwiki/workflows/cli.md index ff6c06c3..8451dfe0 100644 --- a/openwiki/workflows/cli.md +++ b/openwiki/workflows/cli.md @@ -1,37 +1,128 @@ --- type: API Reference -title: Headless CLI — full command tree, JSON output, and exit codes -description: SSHub's scriptable command-line interface (src/cli) covering hosts, exec, groups, identities, tunnels, SFTP, audit, import/export/sync, and completions, with --format json support and stable exit codes 0/1/2 (plus 124 for `exec --timeout`). +title: Headless CLI — command tree, JSON output, and exit codes +description: SSHub's scriptable command-line interface (src/cli) covering hosts, exec, groups, identities, tunnels, SFTP, audit, import/export/sync, the database-free theme commands, and completions, with --format json support and stable exit codes 0/1/2 (plus 124 for `exec --timeout`). resource: src/cli/mod.rs tags: [cli, automation, json, reference, workflow] +verified: + - by: openwiki/0.5.1 + at: 2026-09-11T12:46:43.817Z +sources: + - id: openwiki-source-ca6cb4b1a14fd7969dfae3ec + resource: repo://CHANGELOG.md + - id: openwiki-source-718b318f9818e98ff7c29848 + resource: repo://man/sshub.1 + - id: openwiki-source-23775c3de52f3ab95a13cb8b + resource: repo://README.md + - id: openwiki-source-f531855e3a279281c944b8ad + resource: repo://src/app/util.rs + - id: openwiki-source-a56a3517553e0fea0498853a + resource: repo://src/cli/audit.rs + - id: openwiki-source-1154f6f676a027c7221e5dbb + resource: repo://src/cli/completions.rs + - id: openwiki-source-5822c963076fc548e336ce1e + resource: repo://src/cli/context.rs + - id: openwiki-source-396ca803e3cf512685ab87bc + resource: repo://src/cli/exec.rs + - id: openwiki-source-b9d54129bf6a43a039f3129c + resource: repo://src/cli/help.rs + - id: openwiki-source-16aa81e530496bda7ce97299 + resource: repo://src/cli/host.rs + - id: openwiki-source-8f4cb69338645ec79434e6f8 + resource: repo://src/cli/identity.rs + - id: openwiki-source-066740c1fdbeed847cf52c24 + resource: repo://src/cli/inventory.rs + - id: openwiki-source-2d189e28b5edbe9715440baf + resource: repo://src/cli/mod.rs + - id: openwiki-source-2fa9e6fc6443029e9e46d921 + resource: repo://src/cli/parse.rs + - id: openwiki-source-1fb583e98f32d0ac9cf79bcf + resource: repo://src/cli/sftp.rs + - id: openwiki-source-af2644667d0b644b5c92e850 + resource: repo://src/cli/theme.rs + - id: openwiki-source-9fb4c7b7c40d5ac923917c42 + resource: repo://src/cli/tunnel.rs + - id: openwiki-source-ed8bf05e307c6278442542c2 + resource: repo://src/lib.rs + - id: openwiki-source-b55a21a31ede1b56cd31a6a6 + resource: repo://src/main.rs + - id: openwiki-source-d7d76e3f61266c25b6860f64 + resource: repo://src/profile/mod.rs + - id: openwiki-source-39c7504511d4abe5494f1fca + resource: repo://src/session/askpass.rs + - id: openwiki-source-bf6cda76234f60dd7eecaa7f + resource: repo://src/session/mod.rs + - id: openwiki-source-8c90e650e97e8c2db8feaa52 + resource: repo://src/ssh/host.rs + - id: openwiki-source-ef7bbf76631c2cc5e3fe2e35 + resource: repo://src/store/hosts.rs + - id: openwiki-source-0df867f7b9173cf1c752bc25 + resource: repo://src/theme/validate.rs + - id: openwiki-source-63175631717458c0fde9b563 + resource: repo://tests/smoke/cli_commands.rs + - id: openwiki-source-b98428a371cd21678bac849c + resource: repo://tests/smoke/theme_public_api.rs +generated: { by: "openwiki/0.5.1", at: "2026-09-11T12:46:43.817Z" } --- # Headless CLI -Beyond the TUI, `sshub` exposes a full scriptable CLI. `src/main.rs` dispatches in order: askpass re-exec → `db` subcommand → `cli::is_subcommand` (cheap string check, no bootstrap) → global flags → TUI. `CliContext::bootstrap()` (`src/cli/context.rs`) loads config, opens both databases, builds the resolver and `OsKeyring`, and loads merged hosts — so the CLI shares all state with the TUI ([data model](../architecture/data-model.md)). +Beyond the TUI, `sshub` exposes a full scriptable CLI. `CliContext::bootstrap()` (`src/cli/context.rs`) loads config, opens both databases, builds the resolver and the password store (OS keyring with file fallback), and loads merged hosts — so the CLI shares all state with the TUI ([data model](../architecture/data-model.md)). Parsing is hand-rolled (`src/cli/parse.rs`, no clap); JSON/plain DTOs live in `src/cli/output.rs`. -Parsing is hand-rolled (`src/cli/parse.rs`), output DTOs in `src/cli/output.rs`. Conventions: +## Dispatch order (`src/main.rs`) -- `--format plain|json` on listing/show commands (plain default). -- Exit codes: `0` success, `1` operational failure, `2` usage/bad flags, `124` when `exec --timeout` kills the run. `host connect` and `exec` propagate the child ssh exit code. -- Destructive commands refuse without `--yes`; `sshub db purge` requires `--yes-i-am-stupid`. +`main` runs the gates below in a fixed order; getting them wrong changes what a script observes: + +```mermaid +flowchart TD + A["sshub argv"] --> B{"SSH_ASKPASS_FILE set?"} + B -- "yes" --> C["emit staged secret and exit (askpass re-exec)"] + B -- "no" --> D["split argv at the first --"] + D --> E["extract_startup_flags scans the head only"] + E --> F{"first argument"} + F -- "db" --> G["run_db: purge behind --yes-i-am-stupid"] + F -- "theme" --> H["theme::run — no CliContext, no database"] + F -- "known subcommand" --> I["resolve profile, CliContext::bootstrap, run_subcommand"] + F -- "other" --> J["global --help / --version, unknown positional exits 2"] + J -- "no args" --> K["TUI via run_with"] +``` + +The dispatch above shows the exact main.rs sequence: + +1. **askpass re-exec** — if ssh re-executed the binary as its `SSH_ASKPASS` helper (`SSHUB_ASKPASS_FILE` in the environment), the staged secret is printed and the process exits before argv is ever parsed, so the TUI/CLI never sees that invocation. +2. **profile flags with `--` held back** — `--profile NAME` / `--manage-profiles` are extracted by `profile::extract_startup_flags` before dispatch so they work for both the TUI and headless commands. Everything after the first `--` (a remote command such as `sshub exec web -- aws --profile prod s3 ls`) is split off first and re-appended untouched: otherwise the scan would swallow the remote tool's flags and fail with "unknown profile" or silently drop a flag. `--profile` combined with `--manage-profiles`, or used in compatibility mode (directory overrides), is rejected. +3. **`db`** — handled directly in `main.rs` (unknown `db` subcommand or missing one exits 2). +4. **`theme`** — dispatched *before* `is_subcommand`/`CliContext::bootstrap`: theme commands are headless by contract, so validating a draft never opens the launcher and metadata databases (see below). +5. **other subcommands** — `cli::is_subcommand` is a cheap string check with no bootstrap; only a match leads to profile resolution + `CliContext::bootstrap` + `cli::run_subcommand`. `sshub --help` reaches the per-command help here instead of the global help. +6. **global flags / TUI** — only when no subcommand was given: `--help`, `--version`, an unknown non-flag first arg (exits 2 with a hint instead of launching a full-screen TUI on a typo), `--dry-run` (exit 0 immediately, smoke/CI), then the TUI. + +Headless invocations never show the profile picker: without `--profile` the last-used profile is used; an unknown `--profile` name fails with the list of available profiles. + +## Conventions + +- `--format plain|json` on listing/show commands (plain default). The flag is parsed by scanning the whole arg list, and `theme show` deliberately uses its own `toml|json` set (there is no `plain`, because `show` writes a document, not a report). +- Exit codes: `0` success, `1` operational failure, `2` usage/bad flags, `124` when `exec --timeout` kills the run. `host connect` and `exec` propagate the child ssh exit code (ssh's own failures keep 255); a usage error comes from `parse::usage`, which exits 2. +- Destructive commands refuse without `--yes` (`host delete`, `group delete`, `identity delete`, `tunnel delete`, `sftp rm`); `sshub db purge` requires `--yes-i-am-stupid`. Refusals exit 1 before doing any work. - Unknown positional first arg exits 2 with a hint (avoids launching a full-screen TUI on a typo). +- `--password-stdin` flags read secrets from stdin (trailing newline stripped) rather than argv; `exec` never records the remote command in the audit log because it can carry a secret in an argument. ## Command tree | Command | Subcommands | Notes | |---|---|---| -| `host` (alias `list`, `connect`) | `list show connect resolve search add edit rename delete duplicate` | `add` takes `--name --address --port --username --group --tags`; `connect` runs ssh/mosh as a foreground child process with inherited stdio (Command::spawn + wait), propagating its exit code; it does not use the TUI embedded-PTY session module or the (dead-code) external TerminalLauncher | -| `exec` | — | `sshub exec [--tty] [--timeout SECS] [--format plain\|json] -- `: one non-interactive command on a saved host (`src/cli/exec.rs`). Reuses the connect argv (`session_argv_for_entry` + `prepare_cli_connect_argv`), then adds `-T`/`-tt`, `-o RemoteCommand=none` (a per-host stored remote command must not win, and a config-level one makes ssh refuse the run) and `-o BatchMode=yes` when no stored secret is staged, so a script can never sit on a prompt. stdio passes through, stdin inherited; `--timeout` spawns into its own process group and SIGKILLs the group (a ProxyJump helper would otherwise hold the pipes open). Audited as `via exec`, without the command string. No session transcript; mosh refused | -| `group` (alias `groups`) | `list show add edit delete` | Nested groups via parent | -| `identity` | `list show add edit delete agent-remove` | `add --private-key`, `--password-stdin` for secrets; `agent-remove` = `ssh-add -d` | -| `tunnel` | `list show create start stop delete` | `start` is detached by default (PID files), `--foreground` runs with keep-alive ([tunnels](tunnels.md)) | -| `sftp` | `ls get put rm mkdir rename chmod` | One-shot over a direct host; no ProxyJump ([sessions & SFTP](sessions-sftp.md#sftp)) | -| `audit` | `list stats` | `--status ok|fail`, `--days N`, `--via all|connect|tunnel|agent|exec` (`connect` means interactive connects only) | +| `host` (aliases `list`, `connect`) | `list show connect resolve search add edit rename delete duplicate` | `add` takes `--name --address --port --username --group --tags` plus label/identity/proxy-jump/remote-command/transport/session-log/favorite/`--password-stdin`; `connect` runs ssh/mosh as a foreground child process with inherited stdio (Command::spawn + wait), propagating its exit code and updating `last_connected`; it does not go through the TUI's PTY session module (`src/session/mod.rs`, `Session::spawn`), so there is no transcript unless session logging wraps the argv in `script(1)`. `host add`/`host edit` refuse a `name`/`address`/`username` starting with `-` (see [option-like refusal](#option-like-values-are-refused-at-the-write)) | +| `exec` | — | `sshub exec [--tty] [--timeout SECS] [--format plain\|json] -- `: one non-interactive command on a saved host (`src/cli/exec.rs`), detailed below | +| `group` (alias `groups`, forwards flags) | `list show add edit delete` | Nested groups via `--parent`; `list` hides reserved groups unless `--all`; delete requires `--yes` | +| `identity` | `list show add edit delete agent-remove` | `add --private-key`, `--certificate`, `--password-stdin` for secrets (the piped password is read before the row is created, so an empty secret never sets `has_password`); `agent-remove` = `ssh-add -d` for the identity's key | +| `tunnel` | `list show create start stop delete` | `start` is detached by default (PID files under the profile tunnel dir), `--foreground` runs with the keep-alive reconnect loop ([tunnels](tunnels.md)); `` accepts a tunnel id, label, or local port (ambiguous matches are an error); delete requires `--yes` | + +| `sftp` | `ls get put rm mkdir rename chmod` | One-shot over a direct host via the background SFTP worker; no ProxyJump ([sessions & SFTP](sessions-sftp.md#sftp)); `rm` is destructive and requires `--yes` | +| `audit` | `list stats` | `--status ok|fail|retry`, `--days N`, `--via all|connect|tunnel|agent|exec` (`connect` means interactive connects only — the store's filter is `via NOT IN ('tunnel','agent','exec')`), `--host`, `--limit` (default 50); `stats` counts ok/fail over `--days` (default 30) and only shows `retry` with `--include-retry` | | `tags` | — | List all tags | -| `import` / `sync` / `export` | — | ssh config import, row refresh, `export --stdout|-o` | -| `completions` | `bash zsh fish` | Installed by `just install-completions` | -| `db` | `purge` | Deletes `launcher.db` + sidecars only | +| `theme` | `check list show` | Headless by contract — no TUI, no database, no user-state writes (see below) | +| `import` / `sync` / `export` | — | Multi-format import, ssh_config row refresh, `export --stdout|-o` (see below) | +| `completions` | `bash zsh fish` | `--cache PATH` writes/reads a host-name cache; installed by `just install-completions` | +| `db` | `purge` | Deletes `launcher.db` + SQLite sidecars only (`-wal`, `-shm`, `-journal`) | Examples (from `README.md`): @@ -43,10 +134,45 @@ sshub exec prod-web --timeout 30 -- systemctl is-active nginx sshub audit list --status fail --days 7 ``` -Per-command help: `sshub --help`; the man page (`man/sshub.1`, preview with `just man`) covers the same surface. +Per-command help: `sshub --help` prints a USAGE block scoped to that command (`src/cli/help.rs`); for `exec` only the part before `--` is scanned, so a remote command's own `-h` is not mistaken for a help request. The man page (`man/sshub.1`, preview with `just man`) covers the same surface. + +### `exec` — one scripted command on a saved host + +`sshub exec -- ` reuses the connect argv (`session_argv_for_entry` + `prepare_cli_connect_argv`, so stored identity, credential and ProxyJump apply), then adjusts it (`exec_argv`): + +- `-T`/`-tt`: no PTY by default; `--tty` forces one for full-screen commands (vim, top, less) or `sudo` without NOPASSWD. +- `-o RemoteCommand=none`: a stored remote command must not win. Launcher-managed hosts carry one as a trailing `-- ` in the argv, which is truncated off; an `ssh_config`-level `RemoteCommand` would otherwise make ssh refuse the run outright ("Cannot execute command-line and remote command", exit 255), so the option clears it. +- `-o BatchMode=yes` only when no stored secret is staged: without a secret nothing can answer a prompt (ssh reads prompts from `/dev/tty`, so redirecting stdin does not save a script), but BatchMode would switch the askpass helper off. + +Operational contract: + +- stdio passes through, stdin is inherited in both modes (`echo payload | sshub exec db -- 'psql -f -'` works); the exit code is the remote command's. +- Shell operators belong to whichever shell reads them: a bare `&&` splits locally (`sshub exec web -- ls && uptime` runs `ls` on the host and `uptime` at home — the same as `ssh`); quote the whole thing to send it remotely: `-- 'ls && uptime'`. The quoted form is one argv word after `--`, covered by unit tests and documented in `--help`, the man page and the README since 0.15.2. A full-screen command needs `--tty`. +- `--timeout SECS` mirrors `timeout(1)`: the run is spawned into its own process group and the group is SIGKILLed at the deadline, exiting `124`. The group exists only when a timeout was asked for (otherwise Ctrl-C would stop reaching ssh). Killing the whole group matters because a `ProxyJump` helper (`ssh -W`) inherits the pipes and would keep `--timeout` from ever returning. The local ssh is what gets killed — a remote command started without a PTY can outlive the connection. +- `--format json` buffers the run as `{host, command, exit_code, stdout, stderr, duration_ms}` (piped stdout/stderr drained on their own threads so a chatty command cannot deadlock the timeout poll; stdin stays inherited; lossy UTF-8; a spawn failure still emits a record). Plain streams. +- Never prompts, no session transcript (`script(1)` wrapping fights redirection — use `sshub connect` for one), mosh hosts are refused (no one-shot command mode). Audited as `via exec` without the command string. +- Unknown exec flags before `--` are usage errors (exit 2) rather than silently vanishing; `--timeout` requires a whole-second value ≥ 1. + +### `theme` — headless by contract + +`sshub theme check|list|show` are the only CLI commands that run entirely without a database. `main.rs` dispatches them before `CliContext::bootstrap`, and the commands resolve the themes directory through the read-only `config::config_dir_path` (never `config_dir()`), so asking which themes are installed never creates `~/.config/sshub`, migrates a legacy tree, or touches user state — no `config.toml`, no activation, no file in the themes directory. All validation lives in `crate::theme`; `src/cli/theme.rs` only picks the validation mode and renders. + +- `theme check ` — strict validation (`ValidationMode::Strict`: an unknown role is an error, not a compatibility note) with the same parser/resolver the app uses. The file's own directory stands in for the themes directory, so a portable package whose child inherits from a sibling can be checked as the package it is; a parent that came from a sibling is a warning, because installing the child alone would leave it unresolvable. Diagnostics render as `file:line:column severity: message` (1-based line/column computed from the byte span in the user file's own text) with a `help:` line, including `did you mean \`terminal\`?`-style suggestions: the validator Levenshtein-matches unknown keys/values/role paths against the catalogue with a length-proportional near-miss budget and stable lexicographic tie-breaking. Exit codes: `0` valid (warnings allowed), `1` validation or file error, `2` wrong usage. Plain output escapes C0/DEL control characters from user-authored names (`\u{001b}`-style), and JSON is escaped by serde. +- `theme list` — Compatible-mode registry, exactly what the running app would build. Lists *every* record, including invalid themes and user files that collide with a reserved built-in id (never filtered), plus directory-level diagnostics; a readable themes directory always exits 0, an unreadable one exits 1. +- `theme show ` — `--format toml|json`. Without `--resolved` the theme file is printed verbatim, comments included (the documented copy workflow: `sshub theme show aqua > ~/.config/sshub/themes/aqua-custom.toml`, then `theme check` it); an unknown or invalid id exits 1. With `--resolved` a standalone document is written instead: no `extends`, no references — every semantic slot, gradient (named, with synthetic names for nameless gradients) and component role written out with `toml_edit`-escaped strings and quoted keys, so re-reading the file yields the same runtime theme (round-trip tested for semantic equality). + +### `import` / `sync` / `export` + +- `sshub import [--from ssh|termius|putty|mremoteng] [--dry-run] [PATH]` — `ssh` (default) imports `~/.ssh/config`; PATH is ignored and `--dry-run` is *not supported* for it (refusal exits 1). `termius` takes an export directory containing `L00t.csv` (auto-detected default), `putty` a `.reg` file or a sessions dir (default `~/.putty/sessions`), `mremoteng` a `confCons.xml` (PATH required). `--dry-run` parses and previews the hosts (`name user@host:port` lines plus a count) without writing anything for the other three sources. Only SSH sessions are imported (RDP/VNC/telnet skipped); encrypted mRemoteNG passwords are not decrypted. An unknown `--from` exits 2. +- `sshub sync` — refresh the ssh_config-derived rows in the database, then reload the merged host list. +- `sshub export [--stdout] [-o PATH]` — render launcher hosts as an ssh_config snippet. `--stdout` and `-o` are mutually exclusive; without either, the file lands beside the profile's `config.toml`. + +### Option-like values are refused at the write + +`name`, `address` and `username` all end up in the connection target, and a value starting with `-` is parsed by ssh as a flag (an address of `-oProxyCommand=id` would run `id` locally — issue #101). `create_host` and `update_host` therefore reject option-like values (`host add` surfaces this as a nonzero exit), and every importer refuses such a row too: one poisoned entry does not cost the rest of the file — the summary counts it as `skipped (refused: a field ssh would read as an option)` and the message names the skipped host. At connect time a legacy stored target is additionally neutralised into the `ssh://` form, which OpenSSH refuses loudly instead of parsing as a flag. ## Change guidance -- New subcommand: register in `src/cli/mod.rs` (`is_subcommand` + `run_subcommand`), add a module under `src/cli/`, add its help block in `src/cli/help.rs` and the section in `main.rs::print_help`, list it in `src/cli/completions.rs` (`TOP_LEVEL` plus the bash/zsh/fish bodies), extend the man page, the README table and this page. -- CLI smoke coverage lives in `tests/smoke/cli_commands.rs` (drives the real binary via `assert_cmd`) — see [testing](../testing/strategy.md). -- Keep exit codes stable; scripts depend on them. +- New subcommand: register in `src/cli/mod.rs` (`is_subcommand` + `run_subcommand`; note `db` and `theme` are dispatched in `main.rs` *before* this list — a `theme`-style database-free command needs its own arm there), add a module under `src/cli/`, add its help block in `src/cli/help.rs` and the section in `main.rs::print_help`, list it in `src/cli/completions.rs` (`TOP_LEVEL` plus the bash/zsh/fish bodies — one test per shell asserts the generated scripts offer the subcommands), extend the man page, the README table and this page. +- Keep exit codes stable; scripts depend on them. New usage errors go through `parse::usage` (exit 2) unless the command owns its codes like `theme`. +- CLI smoke coverage lives in `tests/smoke/cli_commands.rs` (drives the real binary via `assert_cmd`, hermetic through `SSHUB_DATA_DIR`/`SSHUB_CONFIG_DIR`/`SSHUB_SSH_CONFIG`) — see [testing](../testing/strategy.md), which points CLI changes at that file. Add an `exec`-style isolation test when a command interacts with global argv scanning or dispatch order. diff --git a/openwiki/workflows/index.md b/openwiki/workflows/index.md index 91083b6a..d86c3363 100644 --- a/openwiki/workflows/index.md +++ b/openwiki/workflows/index.md @@ -1,7 +1,9 @@ # Files -- [Headless CLI — full command tree, JSON output, and exit codes](cli.md) - SSHub's scriptable command-line interface (src/cli) covering hosts, groups, identities, tunnels, SFTP, audit, import/export/sync, and completions, with --format json support and stable exit codes 0/1/2. +- [Broadcast Mode — Run One Command Across a Fleet](broadcast.md) - Broadcast mode runs one command across a host group or tag concurrently — the three-stage wizard with its dry-run barrier, the pure fan-out engine in src/broadcast with a bounded worker pool and BatchMode/SSH_ASKPASS auth, the docked live panel with focus/zoom, auto-dismiss and error toasts, and the one-audit-row-per-host contract. +- [Headless CLI — command tree, JSON output, and exit codes](cli.md) - SSHub's scriptable command-line interface (src/cli) covering hosts, exec, groups, identities, tunnels, SFTP, audit, import/export/sync, the database-free theme commands, and completions, with --format json support and stable exit codes 0/1/2 (plus 124 for `exec --timeout`). - [Known Hosts Manager and Host-Key Verification](known-hosts.md) - The Keys-tab known-hosts overlay lists and filters SSH host keys, exposes fingerprints during first connect, and performs guarded removals through ssh-keygen -R while preserving hashed, marker, and wildcard safety rules. +- [Embedded Terminal Surface — Keys, Mouse, Queries, and Clipboard](session-terminal.md) - The in-band behavior of SSHub's embedded terminal — key encoding under the remote's DECCKM mode, mouse forwarding vs local selection, answers to terminal status queries behind a reply token bucket, the alternate-screen wheel, and the OSC 52 clipboard relay boundary. - [Embedded Sessions & SFTP — PTY sessions, session logging, mosh, and file transfer](sessions-sftp.md) - How SSHub runs SSH inside the TUI via portable-pty + vt100 (src/session), stages credentials through SSH_ASKPASS re-exec, optionally logs session output with rotation, supports mosh transport, and provides a dual-pane SFTP browser over libssh2 with a staged transfer queue (src/sftp). - [TUI Dashboard — Tabs, Overlays, and Keybindings](tui.md) - The keyboard-driven SSHub dashboard — five tabs (hosts, SFTP, tunnels, identities, audit), overlay screens (palette, tag filter, settings, keybind editor, help), default keybindings, and how screens map to AppMode in src/app and src/tui/screens. - [SSH Tunnels — local/remote/dynamic forwards with keep-alive reconnect](tunnels.md) - SSHub defines and manages SSH tunnels (local -L, remote -R, dynamic SOCKS -D), spawns them as monitored child processes with BatchMode/askpass secret staging, and auto-reconnects dropped keep-alive tunnels with exponential backoff configured in config.toml. diff --git a/openwiki/workflows/session-terminal.md b/openwiki/workflows/session-terminal.md new file mode 100644 index 00000000..112fdd26 --- /dev/null +++ b/openwiki/workflows/session-terminal.md @@ -0,0 +1,391 @@ +--- +type: Workflow +title: Embedded Terminal Surface — Keys, Mouse, Queries, and Clipboard +description: The in-band behavior of SSHub's embedded terminal — key encoding under the remote's DECCKM mode, mouse forwarding vs local selection, answers to terminal status queries behind a reply token bucket, the alternate-screen wheel, and the OSC 52 clipboard relay boundary. +resource: src/session/mod.rs +tags: [session, terminal, keys, mouse, clipboard, osc52, workflow] +verified: + - by: openwiki/0.5.1 + at: 2026-09-11T12:46:43.817Z +sources: + - id: openwiki-source-ca6cb4b1a14fd7969dfae3ec + resource: repo://CHANGELOG.md + - id: openwiki-source-ca23c091da29f826613cd7f9 + resource: repo://src/app/mouse.rs + - id: openwiki-source-485a289ed462dcfba5216dee + resource: repo://src/app/session.rs + - id: openwiki-source-085ddc5ae8aff4c9e6d9746b + resource: repo://src/app/tests/session.rs + - id: openwiki-source-f531855e3a279281c944b8ad + resource: repo://src/app/util.rs + - id: openwiki-source-2a737474d86fc75cc9d9694f + resource: repo://src/config.rs + - id: openwiki-source-ed8bf05e307c6278442542c2 + resource: repo://src/lib.rs + - id: openwiki-source-cba68790b7ceee059e92ec35 + resource: repo://src/osc52.rs + - id: openwiki-source-251a566921053d647fd490f2 + resource: repo://src/session/keys.rs + - id: openwiki-source-bf6cda76234f60dd7eecaa7f + resource: repo://src/session/mod.rs + - id: openwiki-source-6c3b297cfe77894e43250929 + resource: repo://src/session/parser.rs + - id: openwiki-source-6ea4bd654665d0a13bbccee4 + resource: repo://src/session/pty.rs + - id: openwiki-source-c4ef9cb3a9b870e829272cef + resource: repo://tests/e2e/session_switcher.rs +generated: { by: "openwiki/0.5.1", at: "2026-09-11T12:46:43.817Z" } +--- + +# Embedded Terminal Surface — Keys, Mouse, Queries, and Clipboard + +Because SSHub renders the remote PTY itself instead of handing the screen to an +external terminal (see [embedded sessions](sessions-sftp.md)), SSHub *is* the +terminal as far as the remote application is concerned. Everything an app inside +the PTY expects a terminal to do — encode keystrokes, route the mouse, answer +status queries, honor an OSC 52 copy — has to exist inside SSHub, and it lives +in four pieces of `src/session`: + +- **`keys.rs`** — outbound encoding: crossterm `KeyEvent`s and `MouseEvent`s to + xterm byte sequences, plus the mouse-routing decision. +- **`parser.rs`** — the `vt100` parser wrapper whose `PtyCallbacks` collect OSC + 52 clipboard writes and synthesize answers to terminal queries that `vt100` + itself does not implement. +- **`mod.rs`** — the `Session` methods that turn those artifacts into PTY + writes (query replies through a token bucket) or host-terminal writes (the + clipboard relay), plus selection and the guarded scroll path. +- **`osc52.rs`** — the single place OSC 52 sequences are framed, shared with + SSHub's own copy shortcuts. + +The [event loop](../architecture/overview.md) drives all of it once per frame: +every session's PTY is drained (`Session::drain`), then the OSC 52 relay pass +runs for the visible session, then input events are dispatched — keys and +pastes to `App::handle_key_session`, mouse to `App::handle_mouse_session` +(`src/app/session.rs`). + +```mermaid +flowchart TD + Ev["crossterm event: key, mouse, or paste"] --> KH["handle_key_session / handle_mouse_session"] + KH --> Enc["keys.rs encoders: encode, encode_mouse, alternate_scroll_keys"] + Enc --> WQ["bounded PTY write queue, 64 entries"] + WQ --> WT["sshub-pty-writer thread writes the PTY master"] + WT --> Rem["ssh and the remote application"] + Rem -->|PTY output| Dr["Session::drain, every frame, every session"] + Dr --> Ps["vt100 parser with PtyCallbacks"] + Ps --> Gr["screen grid, rendered via tui-term"] + Ps --> Cb["OSC 52 clipboard queue, 64 KiB cap, 8 entries"] + Cb --> Gate{"visible session and relay_from_pty on?"} + Gate -- yes --> Host["osc52 framing to the host terminal stdout"] + Gate -- no --> Dis["discard writes and drop counters"] + Ps --> Qr["query answers: DSR 5, DSR 6, DA1"] + Qr --> Tk["reply token bucket, 4096 burst, 256 bytes per second"] + Tk --> WQ +``` + +*The in-band round trip: outbound keystrokes, mouse reports and query answers share one bounded PTY write queue; inbound PTY bytes feed the grid, the clipboard queue and the synthesized query replies.* + +## Key encoding (`src/session/keys.rs`) + +`encode(key, application_cursor)` translates one crossterm key event into the +bytes a remote xterm-compatible shell expects: printable characters (Alt +prepends ESC, Ctrl maps a-z to control codes xterm-style), Enter as `\r`, +Backspace as DEL, Tab / BackTab, Esc, PgUp/PgDn, Insert/Delete, F1–F12, and +modifier combinations. Events with no meaningful encoding — modifier-only +presses, key releases on platforms that report them — yield `None`. + +The `application_cursor` flag is the remote's **DECCKM application-cursor +mode**, read from the session's own vt100 screen right before encoding +(`screen().application_cursor()` in `handle_key_session`). When the remote has +requested it, unmodified arrows, `Home` and `End` are sent as SS3 sequences +(`ESC O A` …); otherwise as the normal CSI form (`ESC [ A`). Modified cursor +keys always use xterm's parameterized CSI form (`ESC [ 1;{param}{key}` with +Shift=+1, Alt=+2, Ctrl=+4), which is unaffected by the mode. + +This closed the 0.11.0 Midnight Commander bug: full-screen ncurses apps set +DECCKM and then expect SS3 cursor keys, and the emulator had tracked the mode +all along — only the key encoder ignored it, so arrows did nothing and +Shift-arrows (a different encoding) were read as "select file". The mode the +emulator already tracked is now what the encoder honors. + +Pastes are a key-shaped special case: `App::handle_paste` forwards the whole +blob to the active session, and `Session::write_paste` wraps it in +`ESC[200~ … ESC[201~` only when the remote requested bracketed-paste mode +(DECSET 2004, e.g. vim). Without the markers a paste into vim's insert mode +re-triggers autoindent and comment continuation per line; without the guard +the markers would leak as literal text into remotes that never asked. + +## Mouse: forward or select (`src/app/session.rs`, `keys.rs`) + +Every mouse event in `AppMode::Connecting` / `AppMode::Session` goes to +`handle_mouse_session`. The routing decision is +`keys::selects_locally(mode, alternate_screen, shift)`: + +| remote mouse mode | alternate screen | Shift | outcome | +|---|---|---|---| +| none | any | any | select locally (nothing to forward) | +| reporting on | yes | no | **forward to the remote app** | +| reporting on | no | no | select text locally | +| reporting on | yes | yes | select text locally (Shift inverts) | +| reporting on | no | yes | **forward to the remote app** (Shift inverts) | + +Forwarding therefore needs **both** conditions: the remote must have requested +mouse reporting *and* be on the alternate screen. Neither is enough alone +(0.15.2, issue #115): + +- A mode without the alternate screen is treated as a **leak**. An app killed + before it could send `ESC[?1002l` leaves reporting on, and every later drag + at the shell prompt used to be encoded and echoed back by the shell as + `0;47;13M` gibberish. Apps that genuinely want the mouse — vim, htop, tmux — + all draw on the alternate screen, so requiring it costs them nothing. +- **Shift inverts the decision either way.** It takes a local selection over a + full-screen app (the same escape hatch a real terminal's Shift+mouse + provides), and it hands the mouse to an app that asked for it without + leaving the primary screen — an inline picker drawn in place, which is + otherwise indistinguishable from a leaked mode. With no mouse mode at all + there is nothing to forward, so Shift changes nothing there. +- A leaked mode at a prompt selects text locally again — the recovery the + whole rule exists for. + +**SSHub's own chrome never reports.** In the forwarding path, events on row 0 +(the session header), rows past the PTY body (the footer), or columns past the +grid are dropped before encoding, so a stray click on SSHub's header or footer +cannot reach the remote as an edge row of the grid (tmux's status line with +`status-position top` was the reported casualty). + +On the forwarding path, `encode_mouse` produces the wire format the remote +asked for: SGR (`ESC [ < {button};{col};{row} M`/`m`, 1-based coordinates, +real button code on release signalled by lowercase `m`) or the default +encoding (`CSI M Cb Cx Cy`, each byte 32 + value, coordinates capped at 223). +Button codes follow xterm: left/middle/right = 0/1/2, wheel = 64/65, drag adds +32, and modifier bits 4/8/16 for Shift/Alt/Ctrl. Events irrelevant under the +active mode are dropped — motion when the remote only wants buttons, releases +in press-only mode. + +On the local path a drag builds an in-app selection over the grid. Rows are +stored as *signed* viewport rows so a drag held past the top or bottom edge +can autoscroll (the poll tick keeps scrolling via +`selection_autoscroll_tick`) without the anchor being clamped away; on +release, `selection_finish` reads the span — walking the scrollback for rows +now off-screen and restoring the view afterwards — and copies it through OSC +52 with a `copied N chars` toast. The [TUI dashboard](tui.md) has its own +mouse routing for dashboard tabs; the two share nothing but the event source. + +## The alternate-screen wheel and PageUp/PageDown + +The alternate screen keeps no scrollback of its own (vt100 gives that grid +zero rows), so the wheel and the paging keys mean something different there +(0.15.2, issue #115): + +- **Wheel on the alternate screen** (local path, Shift not held): a notch + becomes **three arrow keys** — xterm's `alternateScroll`, via + `keys::alternate_scroll_keys`, which encodes the arrows with the remote's + application-cursor mode. Without this the wheel scrolled nothing at all in + `less`, `man` or a remote pager. Shift opts out and keeps the wheel on + SSHub's own scrollback, which is what Shift+wheel means in a real terminal. + A pager started with `-X` (git's default `LESS=FRX`, `journalctl`) never + takes the alternate screen, so `git log` keeps scrolling the local buffer. +- **The once-per-session toast**: `Session::hint_alternate_scroll` sets a + `copy_notice` reading `wheel → ↑ keys here — tmux scrolls with: set -g mouse + on`, on the first notch only. It fires on the notch rather than on a guess + about what is running — from a PTY SSHub sees a byte stream, not processes, + and tmux's status line is the first thing people re-style. Inside tmux the + arrow keys land in the shell's history rather than scrolling anything, so + the toast names the switch that makes tmux scroll its own history; nothing + is sent to the remote and nothing is reconfigured there. +- **PageUp/PageDown**: on the alternate screen the `SessionScrollUp/Down` + actions are suppressed and the keys fall through to `keys::encode`, i.e. + they are forwarded as `ESC[5~`/`ESC[6~` for the app that owns the paging. On + the primary screen they scroll locally, half a screen per press. +- **One guarded path**: both the wheel and PageUp/PageDown scroll through + `Session::scroll_with_selection`, which shifts any live selection by *how + far the view actually moved* — not by what was asked. The view is allowed to + move less than asked (the top of the buffer clamps; the alternate screen has + nothing to scroll), and shifting the selection by the full request anyway + slid the highlight across static rows inside tmux. A wheel that cannot + scroll therefore never drags the highlight. + +## Terminal queries (`src/session/parser.rs`) + +`vt100` implements **no terminal queries at all**: an application that asks +and hears nothing back blocks until its own timeout — atuin's history search +died outright ("The cursor position could not be read within a normal +duration", issue #113), and every crossterm-based TUI stalled two seconds at +startup probing for the kitty keyboard protocol. Over plain `ssh` the real +terminal replies, so SSHub's emulator answers too. Everything unrecognized +lands in `PtyCallbacks::unhandled_csi`, which answers exactly three: + +- **DSR 6 — cursor position** (`CSI 6 n`): the emulator's grid mirrors the + remote screen, so its cursor *is* the answer, reported 1-based and clamped + to the grid size. The clamp matters: vt100 parks the cursor one column past + the right margin after a character lands in the last column (pending wrap), + and unclamped a prompt that fills the line would be reported at column + `cols + 1` — a column the terminal does not have, from which measuring code + underflows. One known gap: with origin mode (DECOM) the row is reported + absolute where a conformant terminal would report it relative to the scroll + region, because vt100 handles DECOM internally and exposes no accessor. +- **DSR 5 — device status** (`CSI 5 n`): always `ESC[0n` — nothing can go + wrong in an in-memory grid, and probes that end on it need *something* to + answer. +- **DA1 — primary device attributes** (`CSI c` / `CSI 0 c`): `ESC[?1;2c` + (VT100 with the advanced video option), the honest floor for what vt100 + emulates. This is also what ends crossterm's kitty-keyboard probe: it waits + for *either* a `?u` reply or DA1, and DA1's arrival plus silence on `?u` is + a definitive "not supported". + +Deliberately **unanswered**: the kitty keyboard protocol query (`CSI ?u`) and +secondary device attributes (`CSI >c`) — private sequences SSHub does not +speak, where claiming support would be worse than silence, and unknown DSR +parameters, where a made-up answer would be parsed as the wrong event. Both +board on `i1.is_some()` / the unmatched parameter and return without a reply. + +Answers accumulate in a bounded buffer (`REPLY_QUEUE_MAX_BYTES`, 1 KiB, whole +replies only — a remote spinning on `CSI 5n` cannot grow the buffer without +bound between drains) and are returned by `ParserState::take_replies()`. +Unlike a clipboard write, a query answer is owed **whether or not the session +is on screen** — the application blocks either way — so `Session::drain` +calls `answer_terminal_queries()` for every session; the visibility discard +path (`discard_clipboard_writes`) explicitly leaves replies alone. The two +share one `PtyCallbacks` struct, which is exactly why a test pins that the +clipboard discard does not discard answers. + +Inside `drain`, the answer pass runs **after** `maybe_send_pending_secret`: +both write the same PTY, and a reply queued ahead of the auto-typed password +would be read as part of it by whatever asked for a line. Answering after +leaves the reply as harmless leftover input instead. + +## The reply token bucket (`src/session/mod.rs`) + +Answers are written back through a token bucket: `REPLY_BURST_BYTES = 4096` +burst allowance, refilled at `REPLY_BYTES_PER_SEC = 256` bytes per second of +elapsed time, capped at the burst. A batch larger than the remaining budget is +skipped whole — half an escape sequence in the remote's input is worse than a +query left unanswered — and neither an over-budget batch nor a failed write +says anything out loud; the application that asked reports its own timeout. + +The remote decides how often it asks, and the answers write into the same +bounded PTY write queue (`WRITE_QUEUE_LEN = 64`, on its own `sshub-pty-writer` +thread) as the user's keystrokes, the auto-typed secret and pastes. A real +application asks a handful of times per keystroke, so neither the burst nor +the sustained rate is ever felt — the sustained rate is a fraction of what a +person typing already puts into the PTY. Unthrottled, a host stuck in a query +loop would fill the write queue on its own and get the typing dropped +instead; keeping answers to a trickle leaves the queue to the person at the +keyboard. + +The bucket is not the freeze fix, and the code says so: measured against the +blocking write, 4096 + 256/s only moved the wedge from under a second to ~80 s +(4096 + 256/s reaching the master's ~20 KiB limit at t≈64 s). What actually +prevents the app freezing is the writer thread — it takes the blocking +`write_all` off the frame loop behind the bounded queue, where a full queue +means nothing is reaching the remote anyway and dropping is the honest outcome +(see [embedded sessions](sessions-sftp.md)). + +## The OSC 52 clipboard relay (`src/session/parser.rs`, `src/osc52.rs`) + +Applications inside the PTY (tmux, neovim with `clipboard=osc52`, lazygit…) +copy by writing `ESC]52;c;BEL` to their stdout — which is the PTY. +vt100 parses that and hands it to `Callbacks::copy_to_clipboard`, whose default +impl silently drops it; SSHub queues it instead so the frame can re-emit it +toward the terminal hosting SSHub (0.13.0). The boundary is deliberately +narrow: + +- **Only the visible session relays.** `App::relay_visible_session_clipboard` + runs once per frame, *before* diagnostics are collected so a failed relay + reaches the SSH log in the same frame. Visibility is `session_is_rendered()` + — the `Session`/`Connecting` modes plus the session and snippet pickers, + which float over a session that keeps rendering behind them. Every other + session gets `discard_clipboard_writes()`: a background tab can neither + change the clipboard now nor replay an old write when it later comes to the + front (drops are discarded with it, so they never surface on promotion). +- **Non-empty writes only.** An empty payload is a clipboard *clear* on + terminals that honor it; it is neither relayed nor counted as a drop — a + remote cannot wipe your clipboard, and nothing was lost worth naming. +- **Reads are refused.** `paste_from_clipboard` is left as the no-op default, + so `ESC]52;c;?BEL` produces nothing: answering would let any host SSHub is + SSH'd into *read* the local clipboard. +- **Size and queue bounds, with named drops.** Payloads over a 64 KiB decoded + cap (`CLIPBOARD_RELAY_MAX_BYTES`, measured by `decoded_len` without actually + decoding) are counted as `oversize`; writes arriving with the 8-entry queue + (`CLIPBOARD_RELAY_MAX_QUEUED`) full are counted as `queue_full`. The two + reasons name different failures — one huge write vs a remote stuck in a copy + loop — so the toast reports them apart: `remote copied N bytes to clipboard`, + optionally `; dropped 1 oversized write` / `clipboard relay dropped … over + the queue limit`. A batch that both copied and dropped says both; drops + alone are still announced. +- **Selector normalized to `c`.** `src/osc52.rs` is the single framing point — + `ESC]52;c;BEL` — and it pins the selector to `c` instead of + forwarding it: a payload vt100 handed over with selector `p` (X11 primary + selection) lands on the host clipboard, because terminals differ wildly in + which selectors they accept and several drop the whole sequence on an + unknown one. vt100 has already validated the base64 alphabet before the + payload arrives, which rules out ESC/BEL injection; the helper adds no + second validation. SSHub's own copy paths — stored-secret copy, zoomed-panel + selection, SSH-log yank — go through the same `write_text`/`write_osc52` + entry, and a test pins that both entry points produce byte-identical output. +- **Opt-out.** `[clipboard] relay_from_pty = false` in `config.toml` drops + every PTY clipboard write, silently (no notice). The default is `true`, and + configs written before the `[clipboard]` section existed keep the default. + The section governs only what the remote may do — SSHub's own copy shortcuts + are unaffected. +- **Session-log caveat.** `Session::drain` appends raw PTY bytes to the + enabled session log before any relay decision, so the raw OSC 52 sequence + can already appear in a session log. The relay neither adds it nor removes + it. (Logs capture everything echoed to the terminal, secrets included — see + [secrets](../security/secrets.md).) + +The relay sink is injectable (`relay_clipboard_writes_with`): the real one, +`crate::osc52::write_b64`, writes to the process's stdout, which the test +harness does not capture — exercising it directly under `cargo test` would +clobber the clipboard of whoever ran the suite. A failed relay sets no success +notice but pushes a `session: clipboard relay failed: …` diagnostic, which the +event loop's connected-session filter keeps. + +## Tests that pin this surface + +- **`src/session/parser.rs`** — the query and relay contract at parser level: + 1-based cursor reports that follow the cursor and clamp at the right margin, + DSR 5, DA1 with and without a parameter, silence for private sequences and + unknown DSR params, the 1 KiB reply cap, OSC 52 queuing/draining, the 64 KiB + oversize and 8-entry queue caps with separately counted drops, empty + payloads ignored, `ESC]52;p;…` reaching the queue (selector normalization + happens in `osc52.rs`), the `?` read refused, invalid base64 ignored, and + both OSC 52 and queries never landing on the grid. +- **`src/session/keys.rs`** — encoding: DECCKM switching cursor keys to SS3, + modified-arrow CSI params, F-key and PgUp/PgDn sequences; the + `selects_locally` truth table (leaked mode at a prompt, full-screen app, + both Shift directions); `alternate_scroll_keys` producing three arrows per + notch in both CSI and SS3; SGR and default mouse encodings, release handling + per mode. +- **`src/session/mod.rs`** — live-PTY tests that spawn real `sh -c` children + at 24×80: a child that asks `CSI 6n` and *reads the answer back* receives + `CPR<1;1R>` (and `CPR<1;80R>` after a full-width line — the clamp, end to + end); `drain()` leaves clipboard writes for the frame to decide on; + discarding keeps query answers; the notice texts for relay + drops, failed + relays, and empty payloads; bracketed-paste wrapping. +- **`src/app/tests/session.rs`** — the relay gate at app granularity: only the + visible session relays, a background session's write is never replayed when + it comes to the front, the dashboard and SFTP view relay nothing, a session + under the picker still counts as visible, and `relay_from_pty = false` + drops everything silently. Also the mouse/scroll regressions: a wheel that + cannot scroll (alternate screen) leaves the selection where it is while the + primary-screen wheel follows the view, and the alternate-screen toast names + `set -g mouse on` once per session (driven at 80×24 via `test_app`). +- **`tests/e2e/session_switcher.rs`** — drives real `sleep` children as + sessions and renders through a ratatui `TestBackend` (120×40), covering the + picker and strip surfaces around the session view; the headless smoke loop + renders one frame on a `TestBackend` at 80×24 (see + [testing](../testing/strategy.md)). + +## Change guidance + +- New terminal query: extend `PtyCallbacks::unhandled_csi` and add a parser + test next to the DSR/DA1 ones; keep silence for anything you cannot answer + truthfully — a wrong answer is worse than none. +- New mouse mode or encoding: `keys.rs` unit tests pin the per-mode event + filtering and both wire formats; extend them before changing the routing + truth table in `selects_locally`. +- Keep OSC 52 framing in `src/osc52.rs` — two callers already share it, and + the selector decision exists exactly once by design. +- Anything that scrolls the session view must go through + `scroll_with_selection` so a selection cannot be shifted farther than the + view moved.