From 62ef9ba9ba7103e62843f9f4f72606ebae3da2bf Mon Sep 17 00:00:00 2001 From: YEONGUK LEE Date: Tue, 27 Oct 2026 09:49:17 +0900 Subject: [PATCH 1/2] fix(deps): reject malformed TOML exponents and prepare 0.5.1 Closes #163. Pin smol-toml1.9.1, keep malformed configuration uncertain before submission, and preserve the finite skill profile and exact-version SSH contracts. --- PARITY.md | 17 ++++ README.ja.md | 16 ++-- README.ko.md | 16 ++-- README.md | 16 ++-- README.zh-CN.md | 16 ++-- RELEASING.md | 44 ++++++++++ VALIDATION.md | 27 ++++++ docs/api.md | 14 ++-- docs/design/typescript-skill-compatibility.md | 19 ++++- docs/guide.ja.md | 30 +++---- docs/guide.ko.md | 34 ++++---- docs/guide.md | 32 +++---- docs/guide.zh-CN.md | 30 +++---- docs/shorthand.md | 2 +- package-lock.json | 12 +-- package.json | 4 +- scripts/release.mjs | 8 +- src/protocol.ts | 2 +- src/skill-metadata.ts | 7 +- test/diagnostics.test.ts | 25 ++++++ test/integration-multihost-updates.test.ts | 14 ++-- test/package-smoke.ts | 2 +- test/release.test.ts | 16 ++-- test/replies.test.ts | 2 +- test/reply-agent-binding.test.ts | 2 +- test/skill-guidance.test.ts | 3 +- test/skill-metadata.test.ts | 30 +++---- test/ssh-hosts.test.ts | 4 +- test/transport.test.ts | 45 ++++++++-- test/updates.test.ts | 84 +++++++++---------- 30 files changed, 371 insertions(+), 202 deletions(-) diff --git a/PARITY.md b/PARITY.md index 30a5f4f..815ae5b 100644 --- a/PARITY.md +++ b/PARITY.md @@ -1,5 +1,22 @@ # Python compatibility and npm migration +## 0.5.1 parser correctness patch — 2026-10-27 KST + +Issue #163 updates only the TOML parser dependency from 1.9.0 to 1.9.1. +Malformed repeated exponent signs such as `numeric_setting = 3e++20` previously +parsed successfully and could make configuration inspection return `default`. +The corrected parser rejects that input, so doctor reports +`unknown / codex_config_unreadable` and Codex send/dry-run refuse before queueing. +Valid exponent signs and the explicit root `sqlite_home` refusal are preserved. +This is a reproduced parser-correctness fix; no storage-relocation bypass or +published vulnerability is claimed. + +The exact 0.5.1 doctor row preserves the four profiles accepted by 0.5.0; +all historical rows and immutable skill pins are unchanged. SSH endpoints must +both use exactly 0.5.1. Unsupported platform/native/automatic-ACK cells and +excluded experiments/reporter/harness remain outside the patch. + + ## 0.5.0 release scope — 2026-10-20 KST The 0.5.0 source carries platform-qualification groundwork. It does not activate additional observation or diff --git a/README.ja.md b/README.ja.md index 84ef708..ecfabac 100644 --- a/README.ja.md +++ b/README.ja.md @@ -14,9 +14,9 @@ -**文書・ソースのバージョン: 0.5.0。** インストール前に、選択したリリースとレジストリのメタデータを確認してください。日付付きの準備・検証記録は [VALIDATION.md](VALIDATION.md)、リリース手順は [RELEASING.md](RELEASING.md) にあります。SSH の両端には同じ TypeScript クライアントのバージョンが必要で、0.3.6 と 0.5.0 は互いの SSH 事前確認を拒否します。 +**文書・ソースのバージョン: 0.5.1。** インストール前に、選択したリリースとレジストリのメタデータを確認してください。日付付きの準備・検証記録は [VALIDATION.md](VALIDATION.md)、リリース手順は [RELEASING.md](RELEASING.md) にあります。SSH の両端には同じ TypeScript クライアントのバージョンが必要で、0.3.6 と 0.5.1 は互いの SSH 事前確認を拒否します。 -**0.4.0 handoff の範囲:** 任意の native 観測は macOS arm64 の厳密な Codex 0.160.1/0.162.1 に限定し、明示的な `operator_confirmed` 受領確認には対応する POSIX ledger を使います。Linux・Windows の native 観測と自動 ACK 収集は未対応です。 0.5.0 のソースは選択できない Linux arm64 の Codex 0.162.1 候補(固定された上流 `aarch64-unknown-linux-musl` 実行ファイル、スキーマ、注入テスト 1 回)を記録しますが、Linux 観測を有効化しません。Linux の native 観測は引き続き未対応です。 +**0.4.0 handoff の範囲:** 任意の native 観測は macOS arm64 の厳密な Codex 0.160.1/0.162.1 に限定し、明示的な `operator_confirmed` 受領確認には対応する POSIX ledger を使います。Linux・Windows の native 観測と自動 ACK 収集は未対応です。 0.5.1 のソースは選択できない Linux arm64 の Codex 0.162.1 候補(固定された上流 `aarch64-unknown-linux-musl` 実行ファイル、スキーマ、注入テスト 1 回)を記録しますが、Linux 観測を有効化しません。Linux の native 観測は引き続き未対応です。 **実行中の Claude Code・Codex セッションを見つけ、ローカルまたは SSH でメッセージを送信します。** Python 不要の Node.js クライアントです。パッケージ名とコマンド名は `session-peer` です。 @@ -57,15 +57,15 @@ SSH は既存の鍵・ホスト信頼設定と両端の**同じバージョン `Get-Command session-peer -All` で PATH を確認してください。利用する実装を選び、 別の管理ツールのファイルを `--force` で上書きしないでください。 -**0.5.0 のインストール:** npm の導入・更新例を実行する前に、レジストリで公開状況と選択したリリースを確認してください。利用できない場合は[レビュー済みソースビルド](docs/guide.ja.md#ソースからビルド)を使います。 +**0.5.1 のインストール:** npm の導入・更新例を実行する前に、レジストリで公開状況と選択したリリースを確認してください。利用できない場合は[レビュー済みソースビルド](docs/guide.ja.md#ソースからビルド)を使います。 ```sh -npm view session-peer@0.5.0 version dist.integrity -npm install --global --ignore-scripts session-peer@0.5.0 +npm view session-peer@0.5.1 version dist.integrity +npm install --global --ignore-scripts session-peer@0.5.1 session-peer --version ``` -期待する出力: `session-peer 0.5.0 (typescript)`。分離インストール・ソースビルド・ +期待する出力: `session-peer 0.5.1 (typescript)`。分離インストール・ソースビルド・ Windows・削除の手順は以下の詳細ガイドを参照してください。 任意の `sp` 短縮名は 0.3.0 以降に含まれ(0.2.1 以前にはありません)、自動では有効になりません。 @@ -74,12 +74,12 @@ Windows・削除の手順は以下の詳細ガイドを参照してください ### Update npm で導入したクライアントは npm で更新します。タグと対象バージョンを確認してから -正確なバージョンを指定してください。以下は古い npm 版を選択した 0.5.0 に更新する例です。 +正確なバージョンを指定してください。以下は古い npm 版を選択した 0.5.1 に更新する例です。 CLI は更新をインストールせず、`session-peer update --check` は確認のみ行います。 ```sh npm view session-peer dist-tags -npm install --global --ignore-scripts session-peer@0.5.0 +npm install --global --ignore-scripts session-peer@0.5.1 session-peer --version ``` diff --git a/README.ko.md b/README.ko.md index 8c1ed33..d6394e6 100644 --- a/README.ko.md +++ b/README.ko.md @@ -19,9 +19,9 @@ Python을 설치하지 않아도 Node.js에서 실행되는 TypeScript 클라이 -**문서·소스 기준 버전: 0.5.0.** 설치 전에 선택한 릴리스와 레지스트리 메타데이터를 확인하세요. 날짜별 준비·검증 기록은 [VALIDATION.md](VALIDATION.md), 릴리스 절차는 [RELEASING.md](RELEASING.md)에 있습니다. SSH 양쪽에는 같은 TypeScript 클라이언트 버전이 필요하며, 0.3.6과 0.5.0은 서로의 SSH 사전 확인을 거부합니다. +**문서·소스 기준 버전: 0.5.1.** 설치 전에 선택한 릴리스와 레지스트리 메타데이터를 확인하세요. 날짜별 준비·검증 기록은 [VALIDATION.md](VALIDATION.md), 릴리스 절차는 [RELEASING.md](RELEASING.md)에 있습니다. SSH 양쪽에는 같은 TypeScript 클라이언트 버전이 필요하며, 0.3.6과 0.5.1은 서로의 SSH 사전 확인을 거부합니다. -**0.4.0 handoff 범위:** 선택 기능인 native 관측은 macOS arm64의 정확한 Codex 0.160.1/0.162.1에 한정되며, 명시적 `operator_confirmed` 수령 확인에는 지원되는 POSIX ledger를 사용합니다. Linux·Windows의 native 관측과 자동 ACK 수집은 미지원입니다. 0.5.0 소스는 선택할 수 없는 Linux arm64 Codex 0.162.1 후보(고정된 업스트림 `aarch64-unknown-linux-musl` 실행 파일, 스키마, 주입 테스트 1회)를 기록하며 Linux 관측을 활성화하지 않습니다. Linux native 관측은 계속 미지원입니다. +**0.4.0 handoff 범위:** 선택 기능인 native 관측은 macOS arm64의 정확한 Codex 0.160.1/0.162.1에 한정되며, 명시적 `operator_confirmed` 수령 확인에는 지원되는 POSIX ledger를 사용합니다. Linux·Windows의 native 관측과 자동 ACK 수집은 미지원입니다. 0.5.1 소스는 선택할 수 없는 Linux arm64 Codex 0.162.1 후보(고정된 업스트림 `aarch64-unknown-linux-musl` 실행 파일, 스키마, 주입 테스트 1회)를 기록하며 Linux 관측을 활성화하지 않습니다. Linux native 관측은 계속 미지원입니다. ## 시연 @@ -66,15 +66,15 @@ PowerShell에서는 `Get-Command session-peer -All`로 PATH(명령 검색 경로 실행 파일을 찾는지 확인하세요. 사용할 구현을 선택하고 다른 패키지 관리자가 설치한 파일을 `--force`로 덮어쓰지 마세요. 설치와 갱신은 사용자가 npm으로 직접 수행합니다. -**0.5.0 설치:** npm 설치·업데이트 예시를 실행하기 전에 레지스트리 제공 여부와 선택한 릴리스를 확인하세요. 해당 버전이 제공되지 않으면 [검토한 소스 빌드](docs/guide.ko.md#소스에서-빌드)를 사용하세요. +**0.5.1 설치:** npm 설치·업데이트 예시를 실행하기 전에 레지스트리 제공 여부와 선택한 릴리스를 확인하세요. 해당 버전이 제공되지 않으면 [검토한 소스 빌드](docs/guide.ko.md#소스에서-빌드)를 사용하세요. ```sh -npm view session-peer@0.5.0 version dist.integrity -npm install --global --ignore-scripts session-peer@0.5.0 +npm view session-peer@0.5.1 version dist.integrity +npm install --global --ignore-scripts session-peer@0.5.1 session-peer --version ``` -예상 결과는 `session-peer 0.5.0 (typescript)`입니다. 별도 경로에 설치하거나, +예상 결과는 `session-peer 0.5.1 (typescript)`입니다. 별도 경로에 설치하거나, 소스에서 빌드하거나, Windows에서 설치·제거하는 방법은 [사용자 가이드](docs/guide.ko.md#설치)를 참고하세요. 선택 사항인 `sp` 단축 명령은 0.3.0 이상에 포함되며(0.2.1 이하에는 없음) 자동으로 @@ -85,13 +85,13 @@ session-peer --version ### 업데이트 npm으로 설치한 클라이언트는 사용자가 직접 npm으로 업데이트합니다. 사용 가능한 배포 태그 -(dist-tag)와 대상 버전을 확인한 뒤 해당 버전을 지정해 설치하세요. 아래 예시는 이전 npm 설치본을 선택한 0.5.0으로 +(dist-tag)와 대상 버전을 확인한 뒤 해당 버전을 지정해 설치하세요. 아래 예시는 이전 npm 설치본을 선택한 0.5.1으로 업데이트합니다. `session-peer` CLI는 업데이트를 설치하지 않습니다. `session-peer update --check`는 새 버전이 있는지 확인할 뿐입니다. ```sh npm view session-peer dist-tags -npm install --global --ignore-scripts session-peer@0.5.0 +npm install --global --ignore-scripts session-peer@0.5.1 session-peer --version ``` diff --git a/README.md b/README.md index 8105462..183369e 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,9 @@ -**Documentation and source version: 0.5.0.** Review the selected release and registry metadata before installation. Dated preparation and validation records are in [VALIDATION.md](VALIDATION.md); release procedures are in [RELEASING.md](RELEASING.md). SSH endpoints require the same TypeScript client version; 0.3.6 and 0.5.0 refuse each other’s SSH preflight. +**Documentation and source version: 0.5.1.** Review the selected release and registry metadata before installation. Dated preparation and validation records are in [VALIDATION.md](VALIDATION.md); release procedures are in [RELEASING.md](RELEASING.md). SSH endpoints require the same TypeScript client version; 0.3.6 and 0.5.1 refuse each other’s SSH preflight. -**0.4.0 handoff scope:** opt-in native observation is limited to exact Codex 0.160.1/0.162.1 on macOS arm64; explicit `operator_confirmed` receipts use supported POSIX ledgers. Linux/Windows native observation and automatic ACK collection remain unsupported. The 0.5.0 source records a non-selectable Linux arm64 Codex 0.162.1 candidate (pinned upstream `aarch64-unknown-linux-musl` executable, schema and one injection test); it does not enable Linux observation. Linux native observation remains unsupported. +**0.4.0 handoff scope:** opt-in native observation is limited to exact Codex 0.160.1/0.162.1 on macOS arm64; explicit `operator_confirmed` receipts use supported POSIX ledgers. Linux/Windows native observation and automatic ACK collection remain unsupported. The 0.5.1 source records a non-selectable Linux arm64 Codex 0.162.1 candidate (pinned upstream `aarch64-unknown-linux-musl` executable, schema and one injection test); it does not enable Linux observation. Linux native observation remains unsupported. **Find and message running Claude Code and Codex sessions, locally or over SSH.** A Node.js client that runs without Python. Package and command: `session-peer`. @@ -57,15 +57,15 @@ Check `type -a session-peer` on macOS/Linux or `Get-Command session-peer -All` in PowerShell before installing. Choose the intended PATH entry; do not use `--force` to overwrite another manager’s files. -**Version 0.5.0 installation:** check registry availability and review the chosen release before running the npm install/update examples. If that version is unavailable, use the [reviewed source build](docs/guide.md#build-from-source). +**Version 0.5.1 installation:** check registry availability and review the chosen release before running the npm install/update examples. If that version is unavailable, use the [reviewed source build](docs/guide.md#build-from-source). ```sh -npm view session-peer@0.5.0 version dist.integrity -npm install --global --ignore-scripts session-peer@0.5.0 +npm view session-peer@0.5.1 version dist.integrity +npm install --global --ignore-scripts session-peer@0.5.1 session-peer --version ``` -Expected: `session-peer 0.5.0 (typescript)`. For isolated installation, source +Expected: `session-peer 0.5.1 (typescript)`. For isolated installation, source builds, Windows and removal, see the detailed guide below. Optional `sp` shorthand ships with 0.3.0 and later (not 0.2.1 or earlier) and is @@ -75,12 +75,12 @@ never enabled automatically. See [explicit activation and collisions](docs/short Use npm for an npm-managed installation. Check the available tags and review the target version, then install that exact version; this example updates an -older npm installation to the selected 0.5.0 release. The CLI does not install updates; +older npm installation to the selected 0.5.1 release. The CLI does not install updates; `session-peer update --check` only reports them. ```sh npm view session-peer dist-tags -npm install --global --ignore-scripts session-peer@0.5.0 +npm install --global --ignore-scripts session-peer@0.5.1 session-peer --version ``` diff --git a/README.zh-CN.md b/README.zh-CN.md index b56d466..1a9c539 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -14,9 +14,9 @@ -**文档和源码版本:0.5.0。** 安装前,请审核所选发布版本和注册表元数据。按日期记录的准备与验证结果见 [VALIDATION.md](VALIDATION.md),发布流程见 [RELEASING.md](RELEASING.md)。SSH 两端必须使用相同的 TypeScript 客户端版本;0.3.6 与 0.5.0 会拒绝彼此的 SSH 预检。 +**文档和源码版本:0.5.1。** 安装前,请审核所选发布版本和注册表元数据。按日期记录的准备与验证结果见 [VALIDATION.md](VALIDATION.md),发布流程见 [RELEASING.md](RELEASING.md)。SSH 两端必须使用相同的 TypeScript 客户端版本;0.3.6 与 0.5.1 会拒绝彼此的 SSH 预检。 -**0.4.0 handoff 范围:** 可选 native 观测仅限 macOS arm64 上精确 Codex 0.160.1/0.162.1 版本;明确的 `operator_confirmed` 收件确认使用受支持的 POSIX ledger。Linux、Windows 的 native 观测和自动 ACK 收集仍不支持。 0.5.0 源码记录了不可选用的 Linux arm64 Codex 0.162.1 候选(固定的上游 `aarch64-unknown-linux-musl` 可执行文件、schema 及一次注入测试),但不启用 Linux 观测;Linux native 观测仍不支持。 +**0.4.0 handoff 范围:** 可选 native 观测仅限 macOS arm64 上精确 Codex 0.160.1/0.162.1 版本;明确的 `operator_confirmed` 收件确认使用受支持的 POSIX ledger。Linux、Windows 的 native 观测和自动 ACK 收集仍不支持。 0.5.1 源码记录了不可选用的 Linux arm64 Codex 0.162.1 候选(固定的上游 `aarch64-unknown-linux-musl` 可执行文件、schema 及一次注入测试),但不启用 Linux 观测;Linux native 观测仍不支持。 **发现正在运行的 Claude Code 和 Codex 会话,并在本机或通过 SSH 发送消息。** 这是不需要 Python 的 Node.js 客户端。包名与命令名均为 `session-peer`。 @@ -55,15 +55,15 @@ session-peer send --to CLAUDE_PID --message '请检查 API 契约并回复。' - `Get-Command session-peer -All` 检查 PATH。选择所需实现,不要用 `--force` 覆盖其他安装管理器的文件。 -**安装 0.5.0:** 运行 npm 安装或更新示例前,请确认注册表可用性并审核所选发布版本。如该版本不可用,请使用[已审核的源码构建](docs/guide.zh-CN.md#从源码构建)。 +**安装 0.5.1:** 运行 npm 安装或更新示例前,请确认注册表可用性并审核所选发布版本。如该版本不可用,请使用[已审核的源码构建](docs/guide.zh-CN.md#从源码构建)。 ```sh -npm view session-peer@0.5.0 version dist.integrity -npm install --global --ignore-scripts session-peer@0.5.0 +npm view session-peer@0.5.1 version dist.integrity +npm install --global --ignore-scripts session-peer@0.5.1 session-peer --version ``` -预期输出:`session-peer 0.5.0 (typescript)`。隔离安装、源码构建、Windows 和 +预期输出:`session-peer 0.5.1 (typescript)`。隔离安装、源码构建、Windows 和 卸载步骤见下方详细指南。 可选的 `sp` 简写包含在 0.3.0 及更高版本中(0.2.1 及更早版本没有),不会自动启用。 @@ -72,11 +72,11 @@ session-peer --version ### Update 使用 npm 更新由 npm 管理的安装。先检查标签并审阅目标版本,再安装指定版本。 -下面示例将旧 npm 安装更新至所选的 0.5.0。CLI 不会安装更新,`session-peer update --check` 只做检查。 +下面示例将旧 npm 安装更新至所选的 0.5.1。CLI 不会安装更新,`session-peer update --check` 只做检查。 ```sh npm view session-peer dist-tags -npm install --global --ignore-scripts session-peer@0.5.0 +npm install --global --ignore-scripts session-peer@0.5.1 session-peer --version ``` diff --git a/RELEASING.md b/RELEASING.md index 670f65e..403da4d 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -9,6 +9,50 @@ the historical 0.3.2 settings-read exception is recorded below. The workflow uses OIDC and no npm token. The protected GitHub environment requires a reviewer and permits deployments from `main` only. +## 0.5.1 dependency hotfix procedure — 2026-10-27 KST + +Issue #163 pins smol-toml 1.9.1 to reject malformed repeated exponent signs in +Codex configuration. It preserves explicit relocated-storage refusal and the +four exact 0.5.0 skill profiles in a finite 0.5.1 row. This is a parser correctness +patch, not a claimed CVE remediation or newly qualified platform/ACK feature. +Draft #155, research #82/#142 and the repository-only reporter #160 are excluded. + +The read-only registry baseline is latest=0.5.0, preview=0.1.0-preview.1 and +0.5.1 absent. Previous stable integrity is +`sha512-lLc7SFRUydY5TjcX8aE18kgfC88Wi4Mx/OXcbLlk0W9yH+JYLkxGwmZIiC7S1bczFgXzsP3xSe8DqiD7YplpDw==`. +Previous provenance endpoint: +`https://registry.npmjs.org/-/npm/v1/attestations/session-peer@0.5.0`. + +1. Merge the reviewed patch under normal protections and require the exact + merged-main release gate, Windows Node 22/24 and CodeQL checks. +2. Freshly verify npm ownership, pending stages, stage-only Trusted Publisher, + package publish-MFA and the protected npm environment/main restriction. + Historical exceptions apply only to their named releases; any unread gate + needs a current explicit user exception. +3. Verify reproducible exact-source archive bytes, all packaged Markdown, + isolated installation/version/removal and default/undefined/inspectSkill + behavior. Both SSH endpoints must use exactly 0.5.1. +4. Obtain source approval naming the exact merged 40-character SHA, + `session-peer@0.5.1`, `stable-stage` and eventual latest promotion to 0.5.1. +5. Dispatch the approved source/version/mode once; inspect retained Actions + bytes before protected-environment approval and stage/provenance before the + separate final npm 2FA approval. Do not retry an uncertain upload. +6. Verify public bytes, registry signatures/provenance and isolated lifecycle, + preserve preview and record publication. Create the authorized v0.5.1 tag + and GitHub Release on the verified publication source. No fleet install. + +## Public 0.5.0 checkpoint — 2026-10-21 KST + +[GitHub v0.5.0](https://github.com/abruption/session-peer-ts/releases/tag/v0.5.0) +and npm 0.5.0 are public from +`12f98f2793b78f647107368c06e83286c5026e05`. +The reviewed/Actions/public tarball SHA-256 is +`50f603d9e5b6db8a99b4bcab464767ad30ff38ecac3fbebc22fc03fdd4c66b1d`; +[stage-only run 39415871999](https://github.com/abruption/session-peer-ts/actions/runs/39415871999) +records publication and human gates. Its GitHub release retains the complete +public validation and settings-read exception record. The following 0.5.0 +procedure is historical and must not be replayed for 0.5.1. + ## 0.5.0 stable release procedure — 2026-10-20 KST The 0.5.0 source includes platform-qualification groundwork from #153, #156 diff --git a/VALIDATION.md b/VALIDATION.md index a52a9cb..3b96be1 100644 --- a/VALIDATION.md +++ b/VALIDATION.md @@ -1,5 +1,32 @@ # Validation record — updated 2026-10-20 KST +## 0.5.1 dependency patch preparation — 2026-10-27 KST + +Baseline source: main `028780338310c9cfe469e1e1587857f730e99a1a`. +The only intervening change from public 0.5.0 is README download-badge wording. +Fresh npm advisory bulk checks across current main and the four open PR locks +reported no advisory; open Dependabot and CodeQL alerts were empty. The scheduled +[audit run](https://github.com/abruption/session-peer-ts/actions/runs/40295976850) +on that exact main passed npm audit and signature/provenance checks on 2026-10-26. +These facts do not prove the absence of every vulnerability. + +The actionable dependency defect is official smol-toml 1.9.1's +[repeated-exponent-sign fix](https://github.com/squirrelchat/smol-toml/releases/tag/v1.9.1). +With installed 1.9.0, `foo=3e++20` and `foo=3e--20` both produced `{foo:3}`. +Two new doctor/transport regressions failed before the update: doctor reported +`available` instead of `unknown`, and send/dry-run did not report +`codex_config_unreadable`. With pinned 1.9.1 both regressions pass, including +root/nested invalid numbers, valid single-sign controls and no fixture queue call +on refusal. This establishes incorrect malformed-config handling, not an actual +relocated-storage bypass, credential exposure or live-agent exploit. + +The tests use isolated fixture homes/queues. No existing user session, remote +host, global installation or native/model/ACK experiment is used. Full package, +profile/release and current-head CI results belong to the patch PR and its exact +source. This dated preparation record does not claim publication or fresh npm +publisher/MFA settings approval. + + ## 0.5.0 preparation scope — 2026-10-20 KST This release preparation includes the merged platform groundwork from #153, diff --git a/docs/api.md b/docs/api.md index 3234060..7aac65b 100644 --- a/docs/api.md +++ b/docs/api.md @@ -1,18 +1,18 @@ # API reference -This reference describes source/package version **0.5.0**. Check registry availability and review the selected release before installation. Dated release evidence is recorded in [VALIDATION.md](../VALIDATION.md); before publication use a reviewed source artifact. +This reference describes source/package version **0.5.1**. Check registry availability and review the selected release before installation. Dated release evidence is recorded in [VALIDATION.md](../VALIDATION.md); before publication use a reviewed source artifact. [Back to README](../README.md) · [User guide](guide.md) The package exposes a small ESM API with TypeScript declarations. Install it -locally with `npm install --ignore-scripts session-peer@0.5.0` before importing it. +locally with `npm install --ignore-scripts session-peer@0.5.1` before importing it. Importing the package does not execute the CLI or submit a message. ```js import { VERSION, VERSION_LINE, reply, envelope } from 'session-peer'; -console.log(VERSION); // 0.5.0 -console.log(VERSION_LINE); // session-peer 0.5.0 (typescript) +console.log(VERSION); // 0.5.1 +console.log(VERSION_LINE); // session-peer 0.5.1 (typescript) const address = 'session-peer://v1/reply?agent=claude&session=12345&transport=local'; console.log(reply(address)); // { to: '12345' } console.log(envelope('Please review and reply.', true, address)); @@ -91,7 +91,7 @@ message/transcript scan manufactures ACK. Ordinary generated Reply-To stays v1. POSIX opt-in `send --host HOST` performs an exact-version preflight and a no-effect preparation before one destination-side send. Both ends must contain -the same reviewed 0.5.0 implementation. This workflow was introduced in +the same reviewed 0.5.1 implementation. This workflow was introduced in 0.4.0; a 0.3.x banner does not establish the capability. Initialize the destination's private ledger explicitly; no command auto-installs a handler or initializes missing history. The origin does not create a local receipt collector. Reconcile with `handoff status`/`wait` on the @@ -193,7 +193,7 @@ requested evidence goal. After any possible RPC write, no legacy fallback or second submission occurs. Selection is by the exact host platform/architecture and native version. -The 0.5.0 source records a Linux arm64 Codex 0.162.1 candidate (pinned musl +The 0.5.1 source records a Linux arm64 Codex 0.162.1 candidate (pinned musl executable, procfs storage binding, `/proc/locks` writer-owner check) but does not activate it. Linux and any other unlisted platform/version have no profile, so required delivery refuses before effect and best-effort uses the @@ -273,7 +273,7 @@ a completed milestone. Exact evidence and limits are in [VALIDATION](../VALIDATI ## 0.4.0 implementation scope -The 0.5.0 source preserves this supported handoff scope; its Linux and Windows +The 0.5.1 source preserves this supported handoff scope; its Linux and Windows groundwork does not enable additional platform or ACK support. The 0.4.0 implementation includes the qualified macOS arm64 observer and explicit diff --git a/docs/design/typescript-skill-compatibility.md b/docs/design/typescript-skill-compatibility.md index f26d068..190c005 100644 --- a/docs/design/typescript-skill-compatibility.md +++ b/docs/design/typescript-skill-compatibility.md @@ -2,7 +2,24 @@ This document retains the original TS source proposal and dated design review for [#118](https://github.com/abruption/session-peer-ts/issues/118), based on the companion [Draft #20 at `963c3d7`](https://github.com/abruption/session-peer-skill/blob/963c3d748a11b1164013ee26c9c0ef285c52b285/docs/typescript-compatibility.md). Companion [design acceptance at `699a325`](https://github.com/abruption/session-peer-skill/blob/699a3251830c30c62d0f812363faf4d0eb0c8e0c/docs/typescript-compatibility.md) records the full static review of the exact candidates below on 2026-10-09. This design-only acceptance does not authorize runtime merge/release, successor skill metadata/publication, installation or pin changes, content authenticity, or live delivery support. -## 0.5.0 finite row — release preparation +## 0.5.1 finite row — 2026-10-27 KST release preparation + +The 0.5.1 source candidate explicitly accepts the same four exact metadata +profiles as public 0.5.0: skill 0.1.0, 0.2.0, 0.2.1 and 0.3.0. Compiled default, +explicit `undefined`, explicit 0.5.1 and outer `inspectSkill` use this finite row. +The literal compatible result remains +`compatible / skill_contract_compatible / metadata_only`. Changed profiles and +unreviewed runtime identities such as 0.5.2 remain incompatible; malformed +runtime identities remain `unknown / skill_metadata_missing` without a +`verification` property. All historical rows remain explicit and unchanged. + +The TOML dependency-correctness patch does not change this metadata parser or +its bounded reader, publish a new skill, adopt another installation pin or add +platform/ACK qualification. Runtime publication still requires its release gates; +these source fixtures are not proof of an actual npm publication. The existing +four-language skill 0.2.1 source/hash below remain unchanged. + +## 0.5.0 finite row — historical release preparation The 0.5.0 source candidate explicitly carries forward the same four exact skill profiles as public 0.4.0: 0.1.0, 0.2.0, 0.2.1 and 0.3.0. The 0.3.0 profile diff --git a/docs/guide.ja.md b/docs/guide.ja.md index 8dc8e0f..460538a 100644 --- a/docs/guide.ja.md +++ b/docs/guide.ja.md @@ -4,11 +4,11 @@ [English](guide.md) | [한국어](guide.ko.md) | [日本語](guide.ja.md) | [简体中文](guide.zh-CN.md) -**文書・ソースのバージョン: 0.5.0。** インストール前に、選択したリリースとレジストリのメタデータを確認してください。日付付きの準備・検証記録は [VALIDATION.md](../VALIDATION.md)、リリース手順は [RELEASING.md](../RELEASING.md) にあります。SSH の両端には同じ TypeScript クライアントのバージョンが必要で、0.3.6 と 0.5.0 は互いの SSH 事前確認を拒否します。 +**文書・ソースのバージョン: 0.5.1。** インストール前に、選択したリリースとレジストリのメタデータを確認してください。日付付きの準備・検証記録は [VALIDATION.md](../VALIDATION.md)、リリース手順は [RELEASING.md](../RELEASING.md) にあります。SSH の両端には同じ TypeScript クライアントのバージョンが必要で、0.3.6 と 0.5.1 は互いの SSH 事前確認を拒否します。 -**0.4.0 handoff の範囲:** 任意の native 観測は macOS arm64 の厳密な Codex 0.160.1/0.162.1 に限定し、明示的な `operator_confirmed` 受領確認には対応する POSIX ledger を使います。Linux・Windows の native 観測と自動 ACK 収集は未対応です。 0.5.0 のソースは選択できない Linux arm64 の Codex 0.162.1 候補(固定された上流 `aarch64-unknown-linux-musl` 実行ファイル、スキーマ、注入テスト 1 回)を記録しますが、Linux 観測を有効化しません。Linux の native 観測は引き続き未対応です。 +**0.4.0 handoff の範囲:** 任意の native 観測は macOS arm64 の厳密な Codex 0.160.1/0.162.1 に限定し、明示的な `operator_confirmed` 受領確認には対応する POSIX ledger を使います。Linux・Windows の native 観測と自動 ACK 収集は未対応です。 0.5.1 のソースは選択できない Linux arm64 の Codex 0.162.1 候補(固定された上流 `aarch64-unknown-linux-musl` 実行ファイル、スキーマ、注入テスト 1 回)を記録しますが、Linux 観測を有効化しません。Linux の native 観測は引き続き未対応です。 -0.5.0 の詳細ガイドです([0.3.1 の信頼性の修正](../PARITY.md#031-reliability-fixes))。0.3.0 は複数ホスト SSH、送信者情報と返信経路、更新確認、任意の `sp` 短縮名を追加しました([0.3.0 の変更点](../PARITY.md#030-remote-usability-and-maintenance))。以前の公開記録は [0.3.1 公開リリースの証拠](../VALIDATION.md#public-031--2026-10-04-kst) にあります。以下の 0.1.0 の説明は旧版との比較です。 +0.5.1 の詳細ガイドです([0.3.1 の信頼性の修正](../PARITY.md#031-reliability-fixes))。0.3.0 は複数ホスト SSH、送信者情報と返信経路、更新確認、任意の `sp` 短縮名を追加しました([0.3.0 の変更点](../PARITY.md#030-remote-usability-and-maintenance))。以前の公開記録は [0.3.1 公開リリースの証拠](../VALIDATION.md#public-031--2026-10-04-kst) にあります。以下の 0.1.0 の説明は旧版との比較です。 @@ -44,7 +44,7 @@ 実行中の **Claude Code と Codex セッション**に、ローカルまたは SSH 経由でメッセージを送る TypeScript クライアントです。Node.js で動作し、Python は不要です。 -**0.5.0 の利用ガイドです。**パッケージ名は `session-peer`、CLI コマンドは **`session-peer`**。Relay サーバーやホスティングサービスは提供しません。 +**0.5.1 の利用ガイドです。**パッケージ名は `session-peer`、CLI コマンドは **`session-peer`**。Relay サーバーやホスティングサービスは提供しません。 ## 機能と範囲 @@ -61,7 +61,7 @@ Relay 通信、MCP、wake/resume、Antigravity、自動更新は未実装です ビルド後の `node dist/cli.js list --json` は Claude/Codex をまとめて表示し、`list --agent codex --json` は既知の home を検索します。公開 npm **0.1.0** では引き続き agent と Codex 一覧の home を明示します。0.2.0 でも明示的なコマンド形式を利用できます。 -対象は既定の `~/.codex`、`CODEX_HOME`、macOS Orca 直下のアカウント home、JSON 配列 `SESSION_PEER_CODEX_HOMES` のみです。`--codex-home` は Codex 一覧を固定して無関係な設定エラーを回避し、`--agent claude` は Codex 探索を省略します。同じ home の別名は統合し、異なる home の同じ UUID は保持します。送信には各行の `codexHome` を使ってください。任意 home の不在はエラーではなく、明示した home の不在・不正は読み取れた行を保持して終了コード 1 を返します。一覧は writer を選択せず、メッセージを提出しません。[一覧契約](../PARITY.md#source-unified-listing-contract--16--020)に順序・診断・`--all`・SSH を記載しています。0.2.0の Codex 送信は `--codex-home` 省略時に一意で安定した live writer を選択します。明示 home でも既知の競合 home をすべて検査します。非アクティブなキュー送信には保存済みスレッド、全候補の非アクティブ検証、および `--codex-home HOME --allow-inactive-codex-home` が必要で、wake/resume は実行しません。Dry-run は提出しません。JSON に診断 `codexHomeResolution` と、ネイティブ出力にある場合のみ `queueId` を追加しますが、消費確認ではありません。[選択契約](../PARITY.md#source-codex-home-selection--17--020)を参照してください。公開 **0.1.0** には明示 live home が必要で、非アクティブ許可オプションはありません。SSH の両端には同じ TypeScript クライアントのバージョン(0.5.0)が必要です。 +対象は既定の `~/.codex`、`CODEX_HOME`、macOS Orca 直下のアカウント home、JSON 配列 `SESSION_PEER_CODEX_HOMES` のみです。`--codex-home` は Codex 一覧を固定して無関係な設定エラーを回避し、`--agent claude` は Codex 探索を省略します。同じ home の別名は統合し、異なる home の同じ UUID は保持します。送信には各行の `codexHome` を使ってください。任意 home の不在はエラーではなく、明示した home の不在・不正は読み取れた行を保持して終了コード 1 を返します。一覧は writer を選択せず、メッセージを提出しません。[一覧契約](../PARITY.md#source-unified-listing-contract--16--020)に順序・診断・`--all`・SSH を記載しています。0.2.0の Codex 送信は `--codex-home` 省略時に一意で安定した live writer を選択します。明示 home でも既知の競合 home をすべて検査します。非アクティブなキュー送信には保存済みスレッド、全候補の非アクティブ検証、および `--codex-home HOME --allow-inactive-codex-home` が必要で、wake/resume は実行しません。Dry-run は提出しません。JSON に診断 `codexHomeResolution` と、ネイティブ出力にある場合のみ `queueId` を追加しますが、消費確認ではありません。[選択契約](../PARITY.md#source-codex-home-selection--17--020)を参照してください。公開 **0.1.0** には明示 live home が必要で、非アクティブ許可オプションはありません。SSH の両端には同じ TypeScript クライアントのバージョン(0.5.1)が必要です。 ### 0.2.0 CLI の使いやすさ @@ -84,15 +84,15 @@ macOS / Linux / Windows native、Node **22.x の 22.13 以上、または 24.x** Node 22.x の 22.13 以上または 24.x を使用し、インストールする正確なバージョンの公開状況を確認してください。Python CLI も同じコマンド名を使うため、既存のインストールがあれば先に PATH を確認してください。 -**0.5.0 のインストール:** npm の導入・更新例を実行する前に、レジストリで公開状況と選択したリリースを確認してください。利用できない場合は[レビュー済みソースビルド](#ソースからビルド)を使います。 +**0.5.1 のインストール:** npm の導入・更新例を実行する前に、レジストリで公開状況と選択したリリースを確認してください。利用できない場合は[レビュー済みソースビルド](#ソースからビルド)を使います。 ```sh -npm view session-peer@0.5.0 version dist.integrity -npm install --global --ignore-scripts session-peer@0.5.0 +npm view session-peer@0.5.1 version dist.integrity +npm install --global --ignore-scripts session-peer@0.5.1 session-peer --version ``` -期待値は `session-peer 0.5.0 (typescript)`。 +期待値は `session-peer 0.5.1 (typescript)`。 1. `session-peer list --agent claude --json` で宛先を探し、正確な PID を選びます。 2. `session-peer send --to CLAUDE_PID --message 'Please reply after checking.' --dry-run --json` で提出せずに確認します。 @@ -111,7 +111,7 @@ npm run build node dist/cli.js --version npm pack --ignore-scripts # 任意のグローバルインストール前に PATH の既存コマンドを確認 -npm install --global --ignore-scripts ./session-peer-0.5.0.tgz +npm install --global --ignore-scripts ./session-peer-0.5.1.tgz session-peer --version ``` @@ -121,7 +121,7 @@ session-peer --version インストール前後に `type -a session-peer` と `command -v session-peer` を確認します。他の実装も同名コマンドを提供するため、PATH 上の一つを選ぶか `node /absolute/path/dist/cli.js` を使ってください。`--force` で他の管理ツールのファイルを上書きしないでください。Python パッケージ・スキル・サービスを自動変更しません。削除は `npm uninstall --global session-peer` を使い、PATH を再確認します。 -Windows PowerShell では `Get-Command session-peer -All` で既存コマンドを確認します。Python CLI を置換せずに試すには、`npm ci --ignore-scripts`、`npm run build`、`npm pack --ignore-scripts` の後、`npm install --prefix "$env:TEMP\session-peer-ts-source" --ignore-scripts .\session-peer-0.5.0.tgz` を実行し、`& "$env:TEMP\session-peer-ts-source\node_modules\.bin\session-peer.cmd" --version` で確認します。同じ prefix の `npm uninstall --prefix "$env:TEMP\session-peer-ts-source" session-peer` で削除します。 +Windows PowerShell では `Get-Command session-peer -All` で既存コマンドを確認します。Python CLI を置換せずに試すには、`npm ci --ignore-scripts`、`npm run build`、`npm pack --ignore-scripts` の後、`npm install --prefix "$env:TEMP\session-peer-ts-source" --ignore-scripts .\session-peer-0.5.1.tgz` を実行し、`& "$env:TEMP\session-peer-ts-source\node_modules\.bin\session-peer.cmd" --version` で確認します。同じ prefix の `npm uninstall --prefix "$env:TEMP\session-peer-ts-source" session-peer` で削除します。 ## 使い方 @@ -212,13 +212,13 @@ Python は開発時の比較基準です(v1.1.0、`8ca77cf31254c8287a2593fe484 ## npm リリース -公開 **0.3.2** は 2026-10-07 KST に検証されました([固定された公開記録](../VALIDATION.md#public-032--2026-10-07-kst))。以下のレジストリ照会で 0.5.0 の現在の公開状況と npm タグを確認してください。日付付きの準備記録は、その後の公開結果を証明しません。 +公開 **0.3.2** は 2026-10-07 KST に検証されました([固定された公開記録](../VALIDATION.md#public-032--2026-10-07-kst))。以下のレジストリ照会で 0.5.1 の現在の公開状況と npm タグを確認してください。日付付きの準備記録は、その後の公開結果を証明しません。 0.2.1 は 2026-09-29 KST、0.2.0 は 2026-09-28 KST に公開・検証されました。 [0.2.1 公開記録](../VALIDATION.md#public-021--2026-09-29-kst)を参照し(以降のリリースはそれぞれ日付付きの記録を追加します)、導入前に正確なバージョンと現在のタグを確認してください。 ```sh -npm view session-peer@0.5.0 version dist.integrity +npm view session-peer@0.5.1 version dist.integrity npm view session-peer dist-tags ``` @@ -260,7 +260,7 @@ session-peer update --check --channel preview --output-format text 実行中の CLI のパスからインストールの管理者を確実に特定できた場合にだけ示します。 対象は、自身の `session-peer` ランチャーがこのパッケージを指す npm グローバル prefix (既定、Homebrew、nvm、nvm-windows、fnm。例: -`npm install --global --ignore-scripts session-peer@0.5.0`)、マニフェストで +`npm install --global --ignore-scripts session-peer@0.5.1`)、マニフェストで `session-peer` を宣言している pnpm・Yarn・Bun のグローバルストア、Volta、npx キャッシュ です。プロジェクトへのインストール(`npm_project`、`pnpm_project`)、ソースチェック アウト(`source`)、それ以外(`unknown`)では `updateCommand: null` と `guidance` の文だけを @@ -355,4 +355,4 @@ native 観測は、macOS arm64 の厳密な Codex 0.160.1/0.162.1 と既存の [Observation](api.md#unreleased-local-delivery-observation--72) -Linux・Windows の native 注入観測は未対応です。 0.5.0 のソースは選択できない Linux arm64 の Codex 0.162.1 候補(固定された上流 `aarch64-unknown-linux-musl` 実行ファイル、スキーマ、注入テスト 1 回)を記録しますが、Linux 観測を有効化しません。Linux の native 観測は引き続き未対応です。対応する POSIX ledger では、運用者が明示的に確認した受領を `operator_confirmed` として記録できますが、自動 ACK 収集ではありません。0.4.0 の範囲は、検証済みの macOS 観測と明示的な運用者確認です。Linux・Windows 観測と receipt bootstrap は [#130](https://github.com/abruption/session-peer-ts/issues/130)、[#131](https://github.com/abruption/session-peer-ts/issues/131)、[#132](https://github.com/abruption/session-peer-ts/issues/132) の後続作業です。 +Linux・Windows の native 注入観測は未対応です。 0.5.1 のソースは選択できない Linux arm64 の Codex 0.162.1 候補(固定された上流 `aarch64-unknown-linux-musl` 実行ファイル、スキーマ、注入テスト 1 回)を記録しますが、Linux 観測を有効化しません。Linux の native 観測は引き続き未対応です。対応する POSIX ledger では、運用者が明示的に確認した受領を `operator_confirmed` として記録できますが、自動 ACK 収集ではありません。0.4.0 の範囲は、検証済みの macOS 観測と明示的な運用者確認です。Linux・Windows 観測と receipt bootstrap は [#130](https://github.com/abruption/session-peer-ts/issues/130)、[#131](https://github.com/abruption/session-peer-ts/issues/131)、[#132](https://github.com/abruption/session-peer-ts/issues/132) の後続作業です。 diff --git a/docs/guide.ko.md b/docs/guide.ko.md index 1a7d358..c6f4395 100644 --- a/docs/guide.ko.md +++ b/docs/guide.ko.md @@ -4,11 +4,11 @@ [English](guide.md) | [한국어](guide.ko.md) | [日本語](guide.ja.md) | [简体中文](guide.zh-CN.md) -**문서·소스 기준 버전: 0.5.0.** 설치 전에 선택한 릴리스와 레지스트리 메타데이터를 확인하세요. 날짜별 준비·검증 기록은 [VALIDATION.md](../VALIDATION.md), 릴리스 절차는 [RELEASING.md](../RELEASING.md)에 있습니다. SSH 양쪽에는 같은 TypeScript 클라이언트 버전이 필요하며, 0.3.6과 0.5.0은 서로의 SSH 사전 확인을 거부합니다. +**문서·소스 기준 버전: 0.5.1.** 설치 전에 선택한 릴리스와 레지스트리 메타데이터를 확인하세요. 날짜별 준비·검증 기록은 [VALIDATION.md](../VALIDATION.md), 릴리스 절차는 [RELEASING.md](../RELEASING.md)에 있습니다. SSH 양쪽에는 같은 TypeScript 클라이언트 버전이 필요하며, 0.3.6과 0.5.1은 서로의 SSH 사전 확인을 거부합니다. -**0.4.0 handoff 범위:** 선택 기능인 native 관측은 macOS arm64의 정확한 Codex 0.160.1/0.162.1에 한정되며, 명시적 `operator_confirmed` 수령 확인에는 지원되는 POSIX ledger를 사용합니다. Linux·Windows의 native 관측과 자동 ACK 수집은 미지원입니다. 0.5.0 소스는 선택할 수 없는 Linux arm64 Codex 0.162.1 후보(고정된 업스트림 `aarch64-unknown-linux-musl` 실행 파일, 스키마, 주입 테스트 1회)를 기록하며 Linux 관측을 활성화하지 않습니다. Linux native 관측은 계속 미지원입니다. +**0.4.0 handoff 범위:** 선택 기능인 native 관측은 macOS arm64의 정확한 Codex 0.160.1/0.162.1에 한정되며, 명시적 `operator_confirmed` 수령 확인에는 지원되는 POSIX ledger를 사용합니다. Linux·Windows의 native 관측과 자동 ACK 수집은 미지원입니다. 0.5.1 소스는 선택할 수 없는 Linux arm64 Codex 0.162.1 후보(고정된 업스트림 `aarch64-unknown-linux-musl` 실행 파일, 스키마, 주입 테스트 1회)를 기록하며 Linux 관측을 활성화하지 않습니다. Linux native 관측은 계속 미지원입니다. -이 가이드는 0.5.0을 다룹니다([0.3.1 신뢰성 수정](../PARITY.md#031-reliability-fixes)). 0.3.0에는 여러 호스트를 통한 SSH 전송, 발신자 정보와 회신 경로, 업데이트 확인, 선택 기능인 `sp` 단축 명령이 추가됐습니다([0.3.0 변경 사항](../PARITY.md#030-remote-usability-and-maintenance)). [0.3.1 공개 릴리스 기록](../VALIDATION.md#public-031--2026-10-04-kst)은 이전 버전의 발행 내역입니다. 아래에서 0.1.0을 언급할 때는 이전 버전을 설명합니다. +이 가이드는 0.5.1을 다룹니다([0.3.1 신뢰성 수정](../PARITY.md#031-reliability-fixes)). 0.3.0에는 여러 호스트를 통한 SSH 전송, 발신자 정보와 회신 경로, 업데이트 확인, 선택 기능인 `sp` 단축 명령이 추가됐습니다([0.3.0 변경 사항](../PARITY.md#030-remote-usability-and-maintenance)). [0.3.1 공개 릴리스 기록](../VALIDATION.md#public-031--2026-10-04-kst)은 이전 버전의 발행 내역입니다. 아래에서 0.1.0을 언급할 때는 이전 버전을 설명합니다. @@ -96,7 +96,7 @@ JSON에는 정제된 `codexHomeResolution`이 포함되며, 네이티브 응답 `queueId`도 포함됩니다. 둘 다 메시지를 읽거나 처리했음을 확인하지 않습니다. [홈 선택 계약](../PARITY.md#source-codex-home-selection--17--020)을 참고하세요. 공개 **0.1.0**은 여전히 실행 중인 홈을 명시해야 하며 비활성 세션 허용 옵션이 없습니다. -SSH를 사용할 때는 발신·수신 양쪽에 같은 TypeScript 클라이언트 버전(0.5.0)을 설치해야 +SSH를 사용할 때는 발신·수신 양쪽에 같은 TypeScript 클라이언트 버전(0.5.1)을 설치해야 합니다. @@ -136,20 +136,20 @@ Claude Code에는 받은 메시지함에 접근할 수 있는 실행 중 TUI(터 Node 22.x의 22.13 이상 또는 24.x를 사용하세요. Python CLI도 같은 `session-peer` 명령 이름을 쓰므로, 설치 전에 PATH(명령 검색 경로)에서 어떤 실행 파일을 찾는지 -확인하세요. 아래 명령은 선택한 0.5.0을 npm으로 설치하는 예입니다. +확인하세요. 아래 명령은 선택한 0.5.1을 npm으로 설치하는 예입니다. CLI 자체는 패키지를 설치하거나 업데이트하지 않습니다. -**0.5.0 설치:** npm 설치·업데이트 예시를 실행하기 전에 레지스트리 제공 여부와 선택한 릴리스를 확인하세요. 해당 버전이 제공되지 않으면 [검토한 소스 빌드](#소스에서-빌드)를 사용하세요. +**0.5.1 설치:** npm 설치·업데이트 예시를 실행하기 전에 레지스트리 제공 여부와 선택한 릴리스를 확인하세요. 해당 버전이 제공되지 않으면 [검토한 소스 빌드](#소스에서-빌드)를 사용하세요. ```sh -npm view session-peer@0.5.0 version dist.integrity -npm install --global --ignore-scripts session-peer@0.5.0 -session-peer --version # session-peer 0.5.0 (typescript) +npm view session-peer@0.5.1 version dist.integrity +npm install --global --ignore-scripts session-peer@0.5.1 +session-peer --version # session-peer 0.5.1 (typescript) session-peer list --agent claude --json session-peer send --to CLAUDE_PID --message 'Please review the API contract.' --dry-run --json ``` -예상 결과는 `session-peer 0.5.0 (typescript)`입니다. +예상 결과는 `session-peer 0.5.1 (typescript)`입니다. 1. 세션을 조회하고 `CLAUDE_PID`를 대상으로 선택한 정확한 PID로 바꾸세요. 2. `--dry-run`으로 검증합니다. 이 단계에서는 메시지를 제출하지 않습니다. @@ -177,11 +177,11 @@ npm run build node dist/cli.js --version npm pack --ignore-scripts # Optional global install: first check which session-peer your PATH selects. -npm install --global --ignore-scripts ./session-peer-0.5.0.tgz +npm install --global --ignore-scripts ./session-peer-0.5.1.tgz session-peer --version ``` -0.5.0에서 예상되는 결과는 `session-peer 0.5.0 (typescript)`입니다. 로컬 빌드 +0.5.1에서 예상되는 결과는 `session-peer 0.5.1 (typescript)`입니다. 로컬 빌드 산출물을 선택하려면 `./...tgz` 경로를 그대로 두세요. 이후 버전에 이 명령을 사용할 때는 먼저 체크아웃한 소스의 버전을 확인하세요. @@ -202,7 +202,7 @@ Python CLI를 덮어쓰지 않으려면 격리 prefix에 설치·제거할 수 npm ci --ignore-scripts npm run build npm pack --ignore-scripts -npm install --prefix "$env:TEMP\session-peer-ts-source" --ignore-scripts .\session-peer-0.5.0.tgz +npm install --prefix "$env:TEMP\session-peer-ts-source" --ignore-scripts .\session-peer-0.5.1.tgz & "$env:TEMP\session-peer-ts-source\node_modules\.bin\session-peer.cmd" --version # Later: npm uninstall --prefix "$env:TEMP\session-peer-ts-source" session-peer ``` @@ -446,7 +446,7 @@ Windows x64/Node 24에서 실제 TUI로 확인한 ACK는 별도의 일회성 증 ## npm 릴리스 -아래 레지스트리 조회로 0.5.0의 현재 제공 여부와 npm 태그를 확인하세요. 날짜가 기록된 준비 근거는 이후 발행 결과를 입증하지 않습니다. +아래 레지스트리 조회로 0.5.1의 현재 제공 여부와 npm 태그를 확인하세요. 날짜가 기록된 준비 근거는 이후 발행 결과를 입증하지 않습니다. 0.3.2는 2026-10-07 KST에, 0.3.1은 2026-10-04 KST에 공개·검증했습니다. [0.3.2 공개 기록](../VALIDATION.md#public-032--2026-10-07-kst)과 @@ -457,7 +457,7 @@ Windows x64/Node 24에서 실제 TUI로 확인한 ACK는 별도의 일회성 증 설치 전에는 정확한 버전과 현재 npm 태그를 확인하세요. ```sh -npm view session-peer@0.5.0 version dist.integrity +npm view session-peer@0.5.1 version dist.integrity npm view session-peer dist-tags ``` @@ -512,7 +512,7 @@ session-peer update --check --channel preview --output-format text `updateCommand`는 실행 중인 CLI 경로로 설치 주체를 확인할 수 있을 때 사용자에게 안내합니다. 해당하는 경우는 자체 `session-peer` 실행기가 이 패키지를 가리키는 npm 전역 prefix(기본, Homebrew, nvm, -nvm-windows, fnm. 예: `npm install --global --ignore-scripts session-peer@0.5.0`), +nvm-windows, fnm. 예: `npm install --global --ignore-scripts session-peer@0.5.1`), 매니페스트에 `session-peer`가 선언된 pnpm·Yarn·Bun 전역 저장소, Volta, npx 캐시입니다. 프로젝트 설치(`npm_project`, `pnpm_project`), 소스 체크아웃(`source`), 그 밖의 경우(`unknown`)에는 `updateCommand: null`과 `guidance` 문장만 돌려주므로, 관련 없는 현재 @@ -608,4 +608,4 @@ native 전송 관찰은 macOS arm64의 정확한 Codex 0.160.1/0.162.1과 이미 [Observation](api.md#unreleased-local-delivery-observation--72) -Linux·Windows의 native 주입 관측은 미지원입니다. 0.5.0 소스는 선택할 수 없는 Linux arm64 Codex 0.162.1 후보(고정된 업스트림 `aarch64-unknown-linux-musl` 실행 파일, 스키마, 주입 테스트 1회)를 기록하며 Linux 관측을 활성화하지 않습니다. Linux native 관측은 계속 미지원입니다. 지원되는 POSIX ledger에서는 운영자가 명시적으로 확인한 수령 사실을 `operator_confirmed`로 기록할 수 있지만, 자동 ACK 수집은 아닙니다. 0.4.0 범위는 검증된 macOS 관측과 명시적 운영자 확인입니다. Linux·Windows 관측과 receipt bootstrap은 각각 [#130](https://github.com/abruption/session-peer-ts/issues/130), [#131](https://github.com/abruption/session-peer-ts/issues/131), [#132](https://github.com/abruption/session-peer-ts/issues/132)에서 후속 작업으로 관리합니다. +Linux·Windows의 native 주입 관측은 미지원입니다. 0.5.1 소스는 선택할 수 없는 Linux arm64 Codex 0.162.1 후보(고정된 업스트림 `aarch64-unknown-linux-musl` 실행 파일, 스키마, 주입 테스트 1회)를 기록하며 Linux 관측을 활성화하지 않습니다. Linux native 관측은 계속 미지원입니다. 지원되는 POSIX ledger에서는 운영자가 명시적으로 확인한 수령 사실을 `operator_confirmed`로 기록할 수 있지만, 자동 ACK 수집은 아닙니다. 0.4.0 범위는 검증된 macOS 관측과 명시적 운영자 확인입니다. Linux·Windows 관측과 receipt bootstrap은 각각 [#130](https://github.com/abruption/session-peer-ts/issues/130), [#131](https://github.com/abruption/session-peer-ts/issues/131), [#132](https://github.com/abruption/session-peer-ts/issues/132)에서 후속 작업으로 관리합니다. diff --git a/docs/guide.md b/docs/guide.md index a2e4ac5..c7bdca7 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -4,11 +4,11 @@ [English](guide.md) | [한국어](guide.ko.md) | [日本語](guide.ja.md) | [简体中文](guide.zh-CN.md) -**Documentation and source version: 0.5.0.** Review the selected release and registry metadata before installation. Dated preparation and validation records are in [VALIDATION.md](../VALIDATION.md); release procedures are in [RELEASING.md](../RELEASING.md). SSH endpoints require the same TypeScript client version; 0.3.6 and 0.5.0 refuse each other’s SSH preflight. +**Documentation and source version: 0.5.1.** Review the selected release and registry metadata before installation. Dated preparation and validation records are in [VALIDATION.md](../VALIDATION.md); release procedures are in [RELEASING.md](../RELEASING.md). SSH endpoints require the same TypeScript client version; 0.3.6 and 0.5.1 refuse each other’s SSH preflight. -**0.4.0 handoff scope:** opt-in native observation is limited to exact Codex 0.160.1/0.162.1 on macOS arm64; explicit `operator_confirmed` receipts use supported POSIX ledgers. Linux/Windows native observation and automatic ACK collection remain unsupported. The 0.5.0 source records a non-selectable Linux arm64 Codex 0.162.1 candidate (pinned upstream `aarch64-unknown-linux-musl` executable, schema and one injection test); it does not enable Linux observation. Linux native observation remains unsupported. +**0.4.0 handoff scope:** opt-in native observation is limited to exact Codex 0.160.1/0.162.1 on macOS arm64; explicit `operator_confirmed` receipts use supported POSIX ledgers. Linux/Windows native observation and automatic ACK collection remain unsupported. The 0.5.1 source records a non-selectable Linux arm64 Codex 0.162.1 candidate (pinned upstream `aarch64-unknown-linux-musl` executable, schema and one injection test); it does not enable Linux observation. Linux native observation remains unsupported. -This guide covers version 0.5.0 ([0.3.1 reliability fixes](../PARITY.md#031-reliability-fixes)). Version 0.3.0 introduced multi-host SSH, sender context and return routes, update checks and the optional `sp` shorthand ([0.3.0 changes](../PARITY.md#030-remote-usability-and-maintenance)). [Public 0.3.1 release evidence](../VALIDATION.md#public-031--2026-10-04-kst) records a prior publication. References to 0.1.0 below describe the older release. +This guide covers version 0.5.1 ([0.3.1 reliability fixes](../PARITY.md#031-reliability-fixes)). Version 0.3.0 introduced multi-host SSH, sender context and return routes, update checks and the optional `sp` shorthand ([0.3.0 changes](../PARITY.md#030-remote-usability-and-maintenance)). [Public 0.3.1 release evidence](../VALIDATION.md#public-031--2026-10-04-kst) records a prior publication. References to 0.1.0 below describe the older release. @@ -81,7 +81,7 @@ Side Session remains experimental. Relay/MCP are outside this comparison. **Find and message running Claude Code and Codex sessions, locally or over SSH.** This TypeScript client runs on Node.js without Python. -This guide describes **version 0.5.0**. The npm package and CLI command are both **`session-peer`**. This +This guide describes **version 0.5.1**. The npm package and CLI command are both **`session-peer`**. This project does not provide a Relay server or hosted service. ## Quick start @@ -89,12 +89,12 @@ project does not provide a Relay server or hosted service. Use Node **22.13+ within 22.x or 24.x**. Check which `session-peer` your PATH selects if the Python CLI is already installed; both packages use that command. -**Version 0.5.0 installation:** check registry availability and review the chosen release before running the npm install/update examples. If that version is unavailable, use the [reviewed source build](#build-from-source). +**Version 0.5.1 installation:** check registry availability and review the chosen release before running the npm install/update examples. If that version is unavailable, use the [reviewed source build](#build-from-source). ```sh -npm view session-peer@0.5.0 version dist.integrity -npm install --global --ignore-scripts session-peer@0.5.0 -session-peer --version # session-peer 0.5.0 (typescript) +npm view session-peer@0.5.1 version dist.integrity +npm install --global --ignore-scripts session-peer@0.5.1 +session-peer --version # session-peer 0.5.1 (typescript) session-peer list --agent claude --json session-peer send --to CLAUDE_PID --message 'Please review the API contract.' --dry-run --json ``` @@ -161,7 +161,7 @@ submits nothing. JSON adds sanitized `codexHomeResolution` and, when supplied by native queue output, `queueId`; neither confirms consumption. See the [selection contract](../PARITY.md#source-codex-home-selection--17--020). Published **0.1.0** still requires an explicit live home and has no inactive opt-in. -On SSH, both ends need the same TypeScript client version (0.5.0). +On SSH, both ends need the same TypeScript client version (0.5.1). ## Requirements @@ -185,11 +185,11 @@ npm run build node dist/cli.js --version npm pack --ignore-scripts # Optional global install: first check which session-peer your PATH selects. -npm install --global --ignore-scripts ./session-peer-0.5.0.tgz +npm install --global --ignore-scripts ./session-peer-0.5.1.tgz session-peer --version ``` -Expected for version 0.5.0: `session-peer 0.5.0 (typescript)`. Keep the `./...tgz` path to +Expected for version 0.5.1: `session-peer 0.5.1 (typescript)`. Keep the `./...tgz` path to select the locally built artifact. Check the checkout version before using these commands for a later release. @@ -209,7 +209,7 @@ On Windows PowerShell, inspect competing commands with npm ci --ignore-scripts npm run build npm pack --ignore-scripts -npm install --prefix "$env:TEMP\session-peer-ts-source" --ignore-scripts .\session-peer-0.5.0.tgz +npm install --prefix "$env:TEMP\session-peer-ts-source" --ignore-scripts .\session-peer-0.5.1.tgz & "$env:TEMP\session-peer-ts-source\node_modules\.bin\session-peer.cmd" --version # Later: npm uninstall --prefix "$env:TEMP\session-peer-ts-source" session-peer ``` @@ -474,7 +474,7 @@ separate approval. Licensed under [MIT](../LICENSE). ## Package release -Public **0.3.2** was verified on 2026-10-07 KST ([immutable checkpoint](../VALIDATION.md#public-032--2026-10-07-kst)). Check current 0.5.0 availability and npm tags with the registry queries below; dated preparation evidence does not establish a later publication outcome. +Public **0.3.2** was verified on 2026-10-07 KST ([immutable checkpoint](../VALIDATION.md#public-032--2026-10-07-kst)). Check current 0.5.1 availability and npm tags with the registry queries below; dated preparation evidence does not establish a later publication outcome. Version 0.2.1 was published and verified on 2026-09-29 KST and 0.2.0 on 2026-09-28 KST; see the [0.2.1 public release record](../VALIDATION.md#public-021--2026-09-29-kst). @@ -482,7 +482,7 @@ Later releases add their own dated records. Check the exact version and current tags before installing: ```sh -npm view session-peer@0.5.0 version dist.integrity +npm view session-peer@0.5.1 version dist.integrity npm view session-peer dist-tags ``` @@ -534,7 +534,7 @@ and reports `current`, `latest`, `channel` (`latest` by default, or `preview`), installation's owner is positively identified from the running CLI's path: an npm global prefix whose own `session-peer` launcher points at this package (default, Homebrew, nvm, nvm-windows and fnm prefixes), for example -`npm install --global --ignore-scripts session-peer@0.5.0`; a pnpm, Yarn or Bun +`npm install --global --ignore-scripts session-peer@0.5.1`; a pnpm, Yarn or Bun global store whose manifest declares `session-peer`; Volta; or the npx cache. Project installs (`npm_project`, `pnpm_project`), source checkouts (`source`) and anything else (`unknown`) get `updateCommand: null` and a `guidance` @@ -635,4 +635,4 @@ Native observation supports exact Codex 0.160.1/0.162.1 on macOS arm64 with an a [Observation](api.md#unreleased-local-delivery-observation--72) -Native delivery observation is unsupported on Linux and Windows. The 0.5.0 source records a non-selectable Linux arm64 Codex 0.162.1 candidate (pinned upstream `aarch64-unknown-linux-musl` executable, schema and one injection test); it does not enable Linux observation. Linux native observation remains unsupported. The supported POSIX ledger can record an explicitly verified receipt as `operator_confirmed`; this is not automatic ACK collection. The 0.4.0 scope is qualified macOS observation and explicit operator confirmation. Linux, Windows and receipt-bootstrap extensions are tracked separately in [#130](https://github.com/abruption/session-peer-ts/issues/130), [#131](https://github.com/abruption/session-peer-ts/issues/131) and [#132](https://github.com/abruption/session-peer-ts/issues/132). +Native delivery observation is unsupported on Linux and Windows. The 0.5.1 source records a non-selectable Linux arm64 Codex 0.162.1 candidate (pinned upstream `aarch64-unknown-linux-musl` executable, schema and one injection test); it does not enable Linux observation. Linux native observation remains unsupported. The supported POSIX ledger can record an explicitly verified receipt as `operator_confirmed`; this is not automatic ACK collection. The 0.4.0 scope is qualified macOS observation and explicit operator confirmation. Linux, Windows and receipt-bootstrap extensions are tracked separately in [#130](https://github.com/abruption/session-peer-ts/issues/130), [#131](https://github.com/abruption/session-peer-ts/issues/131) and [#132](https://github.com/abruption/session-peer-ts/issues/132). diff --git a/docs/guide.zh-CN.md b/docs/guide.zh-CN.md index 8d190e1..3203203 100644 --- a/docs/guide.zh-CN.md +++ b/docs/guide.zh-CN.md @@ -4,11 +4,11 @@ [English](guide.md) | [한국어](guide.ko.md) | [日本語](guide.ja.md) | [简体中文](guide.zh-CN.md) -**文档和源码版本:0.5.0。** 安装前,请审核所选发布版本和注册表元数据。按日期记录的准备与验证结果见 [VALIDATION.md](../VALIDATION.md),发布流程见 [RELEASING.md](../RELEASING.md)。SSH 两端必须使用相同的 TypeScript 客户端版本;0.3.6 与 0.5.0 会拒绝彼此的 SSH 预检。 +**文档和源码版本:0.5.1。** 安装前,请审核所选发布版本和注册表元数据。按日期记录的准备与验证结果见 [VALIDATION.md](../VALIDATION.md),发布流程见 [RELEASING.md](../RELEASING.md)。SSH 两端必须使用相同的 TypeScript 客户端版本;0.3.6 与 0.5.1 会拒绝彼此的 SSH 预检。 -**0.4.0 handoff 范围:** 可选 native 观测仅限 macOS arm64 上精确 Codex 0.160.1/0.162.1 版本;明确的 `operator_confirmed` 收件确认使用受支持的 POSIX ledger。Linux、Windows 的 native 观测和自动 ACK 收集仍不支持。 0.5.0 源码记录了不可选用的 Linux arm64 Codex 0.162.1 候选(固定的上游 `aarch64-unknown-linux-musl` 可执行文件、schema 及一次注入测试),但不启用 Linux 观测;Linux native 观测仍不支持。 +**0.4.0 handoff 范围:** 可选 native 观测仅限 macOS arm64 上精确 Codex 0.160.1/0.162.1 版本;明确的 `operator_confirmed` 收件确认使用受支持的 POSIX ledger。Linux、Windows 的 native 观测和自动 ACK 收集仍不支持。 0.5.1 源码记录了不可选用的 Linux arm64 Codex 0.162.1 候选(固定的上游 `aarch64-unknown-linux-musl` 可执行文件、schema 及一次注入测试),但不启用 Linux 观测;Linux native 观测仍不支持。 -这是 0.5.0 详细指南([0.3.1 可靠性修复](../PARITY.md#031-reliability-fixes))。0.3.0 新增了多主机 SSH、发送者信息与回复路由、更新检查以及可选的 `sp` 简写([0.3.0 变更](../PARITY.md#030-remote-usability-and-maintenance))。此前的发布记录见 [0.3.1 公开发布证据](../VALIDATION.md#public-031--2026-10-04-kst);下文 0.1.0 的说明用于旧版本对比。 +这是 0.5.1 详细指南([0.3.1 可靠性修复](../PARITY.md#031-reliability-fixes))。0.3.0 新增了多主机 SSH、发送者信息与回复路由、更新检查以及可选的 `sp` 简写([0.3.0 变更](../PARITY.md#030-remote-usability-and-maintenance))。此前的发布记录见 [0.3.1 公开发布证据](../VALIDATION.md#public-031--2026-10-04-kst);下文 0.1.0 的说明用于旧版本对比。 @@ -44,7 +44,7 @@ 向正在运行的 **Claude Code 和 Codex 会话**发送消息,支持本机和跨机器 SSH。这是运行于 Node.js 的 TypeScript 客户端,不需要 Python。 -**这是 0.5.0 使用指南。**包名为 `session-peer`,CLI 命令为 **`session-peer`**。本项目不提供 Relay 服务器或托管服务。 +**这是 0.5.1 使用指南。**包名为 `session-peer`,CLI 命令为 **`session-peer`**。本项目不提供 Relay 服务器或托管服务。 ## 功能与边界 @@ -61,7 +61,7 @@ 构建后,`node dist/cli.js list --json` 合并列出 Claude/Codex,`list --agent codex --json` 查找已知 home。公开 npm **0.1.0** 仍要求显式 agent,以及 Codex 列表所需的 home;0.2.0 也支持显式命令格式。 -范围仅限默认 `~/.codex`、`CODEX_HOME`、macOS Orca 下的直接账户 home,以及 JSON 数组 `SESSION_PEER_CODEX_HOMES`。`--codex-home` 固定 Codex 列表并绕过无关配置错误;`--agent claude` 完全跳过 Codex 探索。同一 home 的别名合并,不同 home 的相同 UUID 保留。发送时使用每行的 `codexHome`。可选 home 缺失不是错误;显式 home 缺失或无效会保留成功读取的行,并返回退出码 1。列表不会选择 writer 或提交消息。[列表契约](../PARITY.md#source-unified-listing-contract--16--020)说明排序、诊断、`--all` 和 SSH 行为。0.2.0的 Codex 发送在省略 `--codex-home` 时选择唯一且稳定的 live writer;显式 home 仍会检查所有已知竞争 home。非活动队列提交需要已保存的线程、所有候选均确认非活动,以及 `--codex-home HOME --allow-inactive-codex-home`,不会执行 wake/resume。Dry-run 不提交。JSON 新增诊断 `codexHomeResolution`,且仅在原生输出提供时包含 `queueId`;两者均不代表消费确认。请参阅[选择契约](../PARITY.md#source-codex-home-selection--17--020)。公开 **0.1.0** 仍需要显式 live home,且没有非活动许可选项。SSH 两端需要相同的 TypeScript 客户端版本(0.5.0)。 +范围仅限默认 `~/.codex`、`CODEX_HOME`、macOS Orca 下的直接账户 home,以及 JSON 数组 `SESSION_PEER_CODEX_HOMES`。`--codex-home` 固定 Codex 列表并绕过无关配置错误;`--agent claude` 完全跳过 Codex 探索。同一 home 的别名合并,不同 home 的相同 UUID 保留。发送时使用每行的 `codexHome`。可选 home 缺失不是错误;显式 home 缺失或无效会保留成功读取的行,并返回退出码 1。列表不会选择 writer 或提交消息。[列表契约](../PARITY.md#source-unified-listing-contract--16--020)说明排序、诊断、`--all` 和 SSH 行为。0.2.0的 Codex 发送在省略 `--codex-home` 时选择唯一且稳定的 live writer;显式 home 仍会检查所有已知竞争 home。非活动队列提交需要已保存的线程、所有候选均确认非活动,以及 `--codex-home HOME --allow-inactive-codex-home`,不会执行 wake/resume。Dry-run 不提交。JSON 新增诊断 `codexHomeResolution`,且仅在原生输出提供时包含 `queueId`;两者均不代表消费确认。请参阅[选择契约](../PARITY.md#source-codex-home-selection--17--020)。公开 **0.1.0** 仍需要显式 live home,且没有非活动许可选项。SSH 两端需要相同的 TypeScript 客户端版本(0.5.1)。 ### 0.2.0 CLI 易用性 @@ -83,15 +83,15 @@ macOS、Linux 或 Windows native;Node **22.x 中的 22.13 及以上,或 24.x 使用 Node 22.x 的 22.13 及以上或 24.x,并确认要安装的确切版本是否已发布。Python CLI 也使用同一个命令名;若已有安装,请先检查 PATH。 -**安装 0.5.0:** 运行 npm 安装或更新示例前,请确认注册表可用性并审核所选发布版本。如该版本不可用,请使用[已审核的源码构建](#从源码构建)。 +**安装 0.5.1:** 运行 npm 安装或更新示例前,请确认注册表可用性并审核所选发布版本。如该版本不可用,请使用[已审核的源码构建](#从源码构建)。 ```sh -npm view session-peer@0.5.0 version dist.integrity -npm install --global --ignore-scripts session-peer@0.5.0 +npm view session-peer@0.5.1 version dist.integrity +npm install --global --ignore-scripts session-peer@0.5.1 session-peer --version ``` -预期输出:`session-peer 0.5.0 (typescript)`。 +预期输出:`session-peer 0.5.1 (typescript)`。 1. 用 `session-peer list --agent claude --json` 查找目标并选择准确 PID。 2. 用 `session-peer send --to CLAUDE_PID --message 'Please reply after checking.' --dry-run --json` 验证,不提交。 @@ -110,7 +110,7 @@ npm run build node dist/cli.js --version npm pack --ignore-scripts # 可选:全局安装前先检查 PATH 选择的现有命令 -npm install --global --ignore-scripts ./session-peer-0.5.0.tgz +npm install --global --ignore-scripts ./session-peer-0.5.1.tgz session-peer --version ``` @@ -120,7 +120,7 @@ session-peer --version 安装前后用 `type -a session-peer` 和 `command -v session-peer` 检查实际执行项。其他实现也可能提供同名命令;请选择 PATH 上的一种,或显式运行 `node /absolute/path/dist/cli.js`。不要用 `--force` 覆盖其他管理器的文件。本包不会自动安装、删除或调整 Python 包、技能或服务。卸载使用 `npm uninstall --global session-peer`,随后再次检查 PATH。 -在 Windows PowerShell 中用 `Get-Command session-peer -All` 检查已有命令。为了不替换 Python CLI,可先执行 `npm ci --ignore-scripts`、`npm run build`、`npm pack --ignore-scripts`,再用 `npm install --prefix "$env:TEMP\session-peer-ts-source" --ignore-scripts .\session-peer-0.5.0.tgz` 安装到隔离目录。以 `& "$env:TEMP\session-peer-ts-source\node_modules\.bin\session-peer.cmd" --version` 验证,并可用 `npm uninstall --prefix "$env:TEMP\session-peer-ts-source" session-peer` 卸载。 +在 Windows PowerShell 中用 `Get-Command session-peer -All` 检查已有命令。为了不替换 Python CLI,可先执行 `npm ci --ignore-scripts`、`npm run build`、`npm pack --ignore-scripts`,再用 `npm install --prefix "$env:TEMP\session-peer-ts-source" --ignore-scripts .\session-peer-0.5.1.tgz` 安装到隔离目录。以 `& "$env:TEMP\session-peer-ts-source\node_modules\.bin\session-peer.cmd" --version` 验证,并可用 `npm uninstall --prefix "$env:TEMP\session-peer-ts-source" session-peer` 卸载。 ## 使用 @@ -211,14 +211,14 @@ Python 只用作开发时的比较基准(v1.1.0,`8ca77cf31254c8287a2593fe484 ## npm 版本 -公开 **0.3.2** 于 2026-10-07 KST 完成验证([不可变公开记录](../VALIDATION.md#public-032--2026-10-07-kst))。请用下方注册表查询确认 0.5.0 的当前可用性和 npm 标签;带日期的准备证据不能证明后续发布结果。 +公开 **0.3.2** 于 2026-10-07 KST 完成验证([不可变公开记录](../VALIDATION.md#public-032--2026-10-07-kst))。请用下方注册表查询确认 0.5.1 的当前可用性和 npm 标签;带日期的准备证据不能证明后续发布结果。 0.2.1 已于 2026-09-29 KST、0.2.0 已于 2026-09-28 KST 公开并验证。请参阅 [0.2.1 公开记录](../VALIDATION.md#public-021--2026-09-29-kst)(之后的版本各自追加带日期的记录), 安装前核对准确版本与当前标签。 ```sh -npm view session-peer@0.5.0 version dist.integrity +npm view session-peer@0.5.1 version dist.integrity npm view session-peer dist-tags ``` @@ -257,7 +257,7 @@ dist-tag(3 秒超时,不重试),并报告 `current`、`latest`、`channe `ahead`)、`managedBy`、`updateCommand` 和 `guidance`。只有根据当前 CLI 路径能确切识别 安装的管理方时才给出命令:其自身 `session-peer` 启动器指向本包的 npm 全局 prefix (默认、Homebrew、nvm、nvm-windows、fnm,例如 -`npm install --global --ignore-scripts session-peer@0.5.0`),清单中声明了 +`npm install --global --ignore-scripts session-peer@0.5.1`),清单中声明了 `session-peer` 的 pnpm、Yarn、Bun 全局存储,Volta,或 npx 缓存。项目安装 (`npm_project`、`pnpm_project`)、源码检出(`source`)以及其他情况(`unknown`)只返回 `updateCommand: null` 和一句 `guidance`,因此不会给出可能修改无关当前目录的命令。它只报告 @@ -339,4 +339,4 @@ native 观测仅支持 macOS arm64 上的精确 Codex 0.160.1/0.162.1 和已有 [Observation](api.md#unreleased-local-delivery-observation--72) -Linux、Windows 的 native 注入观察尚不支持。 0.5.0 源码记录了不可选用的 Linux arm64 Codex 0.162.1 候选(固定的上游 `aarch64-unknown-linux-musl` 可执行文件、schema 及一次注入测试),但不启用 Linux 观测;Linux native 观测仍不支持。支持的 POSIX ledger 可将操作人员明确确认的收件事实记录为 `operator_confirmed`,但这不是自动 ACK 收集。0.4.0 范围是已验证的 macOS 观察和操作人员的明确确认。Linux、Windows 观察及 receipt bootstrap 分别作为 [#130](https://github.com/abruption/session-peer-ts/issues/130)、[#131](https://github.com/abruption/session-peer-ts/issues/131)、[#132](https://github.com/abruption/session-peer-ts/issues/132) 的后续工作。 +Linux、Windows 的 native 注入观察尚不支持。 0.5.1 源码记录了不可选用的 Linux arm64 Codex 0.162.1 候选(固定的上游 `aarch64-unknown-linux-musl` 可执行文件、schema 及一次注入测试),但不启用 Linux 观测;Linux native 观测仍不支持。支持的 POSIX ledger 可将操作人员明确确认的收件事实记录为 `operator_confirmed`,但这不是自动 ACK 收集。0.4.0 范围是已验证的 macOS 观察和操作人员的明确确认。Linux、Windows 观察及 receipt bootstrap 分别作为 [#130](https://github.com/abruption/session-peer-ts/issues/130)、[#131](https://github.com/abruption/session-peer-ts/issues/131)、[#132](https://github.com/abruption/session-peer-ts/issues/132) 的后续工作。 diff --git a/docs/shorthand.md b/docs/shorthand.md index 5ba29bf..d25cc83 100644 --- a/docs/shorthand.md +++ b/docs/shorthand.md @@ -1,6 +1,6 @@ # Optional `sp` shorthand -This reference describes source/package version 0.5.0. Check registry availability before using a version-pinned npm installation; before publication use a reviewed source artifact. The shorthand forwards to the explicitly selected CLI and does not install or upgrade it. +This reference describes source/package version 0.5.1. Check registry availability before using a version-pinned npm installation; before publication use a reviewed source artifact. The shorthand forwards to the explicitly selected CLI and does not install or upgrade it. [README](../README.md) · [User guide](guide.md) diff --git a/package-lock.json b/package-lock.json index 176e0dc..678057f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "session-peer", - "version": "0.5.0", + "version": "0.5.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "session-peer", - "version": "0.5.0", + "version": "0.5.1", "license": "MIT", "os": [ "darwin", @@ -15,7 +15,7 @@ ], "dependencies": { "fs-ext-extra-prebuilt": "2.2.14", - "smol-toml": "1.9.0" + "smol-toml": "1.9.1" }, "bin": { "session-peer": "dist/cli.js" @@ -58,9 +58,9 @@ "license": "MIT" }, "node_modules/smol-toml": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.9.0.tgz", - "integrity": "sha512-hpd+HLON7HdZXqYchMM/+LaTTbdK0AU3NngIJ4KVyWbY9bfQqdL9cD+4yf6dUoU2Ap4VsU0JkQi6FxAI1B2mXQ==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.9.1.tgz", + "integrity": "sha512-j7WyPdf0CJoutMzMoPLF/bVXHtKZIT9Rb4eGeEbWw8X2t5oirjPgpL+OpaENZrILYaXqYwnVH+qgUwVbl+pUJQ==", "license": "BSD-3-Clause", "engines": { "node": ">= 18" diff --git a/package.json b/package.json index eecc421..ff97596 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "session-peer", - "version": "0.5.0", + "version": "0.5.1", "private": false, "publishConfig": { "registry": "https://registry.npmjs.org/", @@ -50,6 +50,6 @@ }, "dependencies": { "fs-ext-extra-prebuilt": "2.2.14", - "smol-toml": "1.9.0" + "smol-toml": "1.9.1" } } diff --git a/scripts/release.mjs b/scripts/release.mjs index 7266ce8..e66dd91 100644 --- a/scripts/release.mjs +++ b/scripts/release.mjs @@ -8,9 +8,9 @@ import { fileURLToPath } from 'node:url'; export const repository = 'abruption/session-peer-ts'; export const registry = 'https://registry.npmjs.org'; -const stableVersion = '0.5.0'; -const previousStable = '0.4.0'; -const previousStableIntegrity = 'sha512-mMNJ118atoflhpFTkf2+DtO7MRf7FQnPK3lKWbq/vd8Xvb5DsjUtAgydIBOcd3Xj9BCYGqM50aFhzUr4dDGHSw=='; +const stableVersion = '0.5.1'; +const previousStable = '0.5.0'; +const previousStableIntegrity = 'sha512-lLc7SFRUydY5TjcX8aE18kgfC88Wi4Mx/OXcbLlk0W9yH+JYLkxGwmZIiC7S1bczFgXzsP3xSe8DqiD7YplpDw=='; const verifiedPreview = '0.1.0-preview.1'; const verifiedPreviewIntegrity = 'sha512-h4SMvrQ/LWA9osd4EHIs9rSTqv1u+S3MXQAmn+yG/gZ9+7NwYMutq+Oa5K/0ggIq61Wfwdh11Cv+5YdEffiNMA=='; export const packageFiles = ['UNICODE-LICENSE.txt', 'dist/casefold.js', 'dist/casefold.d.ts', 'dist/help.js', 'dist/help.d.ts', 'dist/output.js', 'dist/output.d.ts', 'CONTRIBUTING.md', 'LICENSE', 'PARITY.md', 'README.ja.md', 'README.ko.md', @@ -30,7 +30,7 @@ export function validatePackage(pkg) { assert.deepEqual(pkg.exports, { '.': { types: './dist/index.d.ts', import: './dist/index.js' } }); assert.equal(pkg.private, false); assert.ok(pkg.version === stableVersion || /^\d+\.\d+\.\d+-preview\.\d+$/.test(pkg.version), - 'only reviewed preview versions and 0.5.0 stable are supported'); + 'only reviewed preview versions and 0.5.1 stable are supported'); assert.deepEqual(pkg.bin, { 'session-peer': 'dist/cli.js' }); assert.equal(pkg.repository.url, `git+https://github.com/${repository}.git`); assert.deepEqual(pkg.publishConfig, { registry: `${registry}/`, access: 'public', diff --git a/src/protocol.ts b/src/protocol.ts index e98d811..b1f4893 100644 --- a/src/protocol.ts +++ b/src/protocol.ts @@ -1,7 +1,7 @@ import { isIPv6 } from 'node:net'; import { Refusal } from './discovery.js'; import { uuid } from './writer.js'; -export const VERSION = '0.5.0'; +export const VERSION = '0.5.1'; export const VERSION_LINE = `session-peer ${VERSION} (typescript)`; // Return the OpenSSH destination: [user@]name, or an IPv6 literal with any // brackets removed (`ssh` does not strip `[...]` outside ssh:// URIs). Zone IDs diff --git a/src/skill-metadata.ts b/src/skill-metadata.ts index fd23725..6cc3442 100644 --- a/src/skill-metadata.ts +++ b/src/skill-metadata.ts @@ -11,15 +11,16 @@ const verdict = (compatible: boolean): SkillCheck => ({ status: compatible ? 'co code: compatible ? 'skill_contract_compatible' : 'skill_contract_mismatch', verification: 'metadata_only' }); export const SKILL_METADATA_LIMIT = 65536; const keys = ['version', 'runtime-implementation', 'runtime-min-version', 'runtime-full-version', 'runtime-capability-policy'] as const; -// Preserve each published runtime/profile result. The 0.5.0 release candidate -// carries forward only the four exact profiles accepted by public 0.4.0. +// Preserve each published runtime/profile result. The 0.5.1 release candidate +// carries forward only the four exact profiles accepted by public 0.5.0. // Metadata compatibility does not authenticate content or publish/install a skill. // Every later runtime/profile requires separate review; no version ranges. const matrix: Record = { '0.3.2': ['0.1.0'], '0.3.3': ['0.1.0', '0.2.0'], '0.3.4': ['0.1.0', '0.2.0'], '0.3.5': ['0.1.0', '0.2.0', '0.2.1'], '0.3.6': ['0.1.0', '0.2.0', '0.2.1'], '0.4.0': ['0.1.0', '0.2.0', '0.2.1', '0.3.0'], - '0.5.0': ['0.1.0', '0.2.0', '0.2.1', '0.3.0'] + '0.5.0': ['0.1.0', '0.2.0', '0.2.1', '0.3.0'], + '0.5.1': ['0.1.0', '0.2.0', '0.2.1', '0.3.0'] }; const profiles: Record = { '0.1.0': ['0.1.0', 'typescript', '0.1.0', '0.1.0', 'probe-help'], diff --git a/test/diagnostics.test.ts b/test/diagnostics.test.ts index f53ad1b..915a635 100644 --- a/test/diagnostics.test.ts +++ b/test/diagnostics.test.ts @@ -227,3 +227,28 @@ test('doctor distinguishes nested sqlite_home keys from an escaped root override assert.equal(result.sendAuthorized, false); } }); + + +test('doctor rejects repeated exponent signs anywhere in Codex configuration', async t => { + const f = fixture(t); f.db(); + const config = join(f.home, 'config.toml'); + // Unrelated numeric settings are still part of the complete TOML document. + // A rejected document cannot establish that default SQLite storage is in use. + for (const number of ['3e+20', '3e-20']) { + writeFileSync(config, `numeric_setting = ${number}\n[profiles.p]\nweight = ${number}\n`); + const result = await diagnoseCodex(f.home, process.execPath) as any; + assert.equal(result.homes[0].status, 'available', number); + assert.equal(result.homes[0].code, 'state_db_readable', number); + assert.equal(result.ready, true); assert.equal(result.tool.executed, false); + } + for (const number of ['3e++20', '3e--20']) { + for (const text of [`numeric_setting = ${number}\n`, `[profiles.p]\nweight = ${number}\n`]) { + writeFileSync(config, text); + const result = await diagnoseCodex(f.home, process.execPath) as any; + assert.equal(result.homes[0].status, 'unknown', text); + assert.equal(result.homes[0].code, 'codex_config_unreadable', text); + assert.equal(result.ready, false); assert.equal(result.tool.executed, false); + assert.equal(result.sendAuthorized, false); + } + } +}); diff --git a/test/integration-multihost-updates.test.ts b/test/integration-multihost-updates.test.ts index e50252e..9374c9a 100644 --- a/test/integration-multihost-updates.test.ts +++ b/test/integration-multihost-updates.test.ts @@ -21,7 +21,7 @@ async function fixture(t: TestContext) { const requests: string[] = []; const server = createServer((request, response) => { requests.push(request.url ?? ''); - response.writeHead(200, { 'content-type': 'application/json' }); response.end(JSON.stringify({ latest: '0.5.1' })); + response.writeHead(200, { 'content-type': 'application/json' }); response.end(JSON.stringify({ latest: '0.5.2' })); }); server.listen(0, '127.0.0.1'); await once(server, 'listening'); t.after(async () => { @@ -38,7 +38,7 @@ const args=process.argv.slice(2); if(args.includes('-G')){console.log('hostname x\\nuser config-user\\nport 22');process.exit(0);} const dest=args[args.indexOf('--')+1]; if((process.env.FAKE_DOWN||'').split(',').includes(dest)){console.error('ssh: connect to host: Connection refused');process.exit(255);} -if(args.at(-1).endsWith('--version')){console.log('session-peer 0.5.0 (typescript)');process.exit(0);} +if(args.at(-1).endsWith('--version')){console.log('session-peer 0.5.1 (typescript)');process.exit(0);} const r=spawnSync(${JSON.stringify(process.execPath)},[${JSON.stringify(cli)},'--stdio-request'],{input:fs.readFileSync(0,'utf8'),encoding:'utf8', env:{...process.env,SESSION_PEER_CACHE_DIR:${JSON.stringify(receiver)},SESSION_PEER_UPDATE_NOTICE:'1'}}); // FAKE_INJECT: a receiver response carrying a forged client-only field. @@ -58,7 +58,7 @@ process.stdout.write(out);process.exit(r.status);`, { mode: 0o700 }); }; const seed = (age = 0) => { mkdirSync(cache, { recursive: true, mode: 0o700 }); - writeFileSync(file, JSON.stringify({ schemaVersion: 1, package: 'session-peer', source: 'npm_registry', channel: 'latest', latest: '0.5.1', checkedAt: Date.now() - age }), { mode: 0o600 }); + writeFileSync(file, JSON.stringify({ schemaVersion: 1, package: 'session-peer', source: 'npm_registry', channel: 'latest', latest: '0.5.2', checkedAt: Date.now() - age }), { mode: 0o600 }); }; const settled = async (count: number) => { for (let i = 0; i < 200 && !(requests.length >= count && existsSync(file) && !existsSync(lock)); i++) await delay(50); @@ -86,7 +86,7 @@ test('repeated --host keeps the request-order array exactly; the notice is clien // One destination stays flat and gets the additive client field. const single = await f.call(['list', '--agent', 'claude', '--host', 'alpha', '--json']); const flat = JSON.parse(single.stdout); - assert.equal(Array.isArray(flat), false); assert.equal(flat.host, 'alpha'); assert.equal(flat.clientUpdate.latest, '0.5.1'); + assert.equal(Array.isArray(flat), false); assert.equal(flat.host, 'alpha'); assert.equal(flat.clientUpdate.latest, '0.5.2'); await delay(500); assert.deepEqual(f.requests, [], 'fresh cache: no refresh'); assert.equal(existsSync(f.receiver), false); }); @@ -98,7 +98,7 @@ test('each invocation schedules at most one refresh regardless of host count; re for (const item of JSON.parse(first.stdout)) assert.equal('clientUpdate' in item, false); await f.settled(1); assert.equal(f.requests.length, 1, 'one refresh for three hosts'); - assert.equal(JSON.parse(readFileSync(f.file, 'utf8')).latest, '0.5.1'); + assert.equal(JSON.parse(readFileSync(f.file, 'utf8')).latest, '0.5.2'); f.seed(25 * 3600_000); await f.call(f.args(['--output-format', 'text'])); await f.settled(2); @@ -118,7 +118,7 @@ test('failure policy: per-host results (including failed ones) may carry the adv const on = await f.call(['list', '--agent', 'claude', '--host', 'alpha', '--json'], down); const offValue = JSON.parse(off.stdout), { clientUpdate, ...onValue } = JSON.parse(on.stdout); assert.equal(offValue.ok, false); assert.equal(offValue.error, 'ssh_unreachable'); - assert.equal(on.code, off.code); assert.deepEqual(onValue, offValue); assert.equal(clientUpdate.latest, '0.5.1'); + assert.equal(on.code, off.code); assert.deepEqual(onValue, offValue); assert.equal(clientUpdate.latest, '0.5.2'); // Mixed array: element shape and exit code unchanged; text gets one stderr line for the invocation. const mixedOff = await f.call(f.args(['--json', '--no-update-notice']), down), mixedOn = await f.call(f.args(['--json']), down); assert.equal(mixedOn.code, mixedOff.code); assert.deepEqual(JSON.parse(mixedOn.stdout), JSON.parse(mixedOff.stdout)); @@ -152,7 +152,7 @@ test('clientUpdate is client-local: a remote-supplied value is dropped', posix, assert.equal(localRun.code, 0); const { clientUpdate: own, ...local } = JSON.parse(localRun.stdout); accepted(local, 'alpha'); - assert.equal(own.source, 'npm_registry_cache'); assert.equal(own.latest, '0.5.1'); assert.notEqual(own.command, 'fixture-only'); + assert.equal(own.source, 'npm_registry_cache'); assert.equal(own.latest, '0.5.2'); assert.notEqual(own.command, 'fixture-only'); const text = await f.call(f.args(['--output-format', 'text', '--no-update-notice']), inject); assert.equal(text.code, 0); assert.doesNotMatch(text.stdout, /fixture-only|Error:/); assert.equal(text.stderr, ''); assert.deepEqual(text.stdout.match(/^Host: .+$/gm), hosts.map(item => `Host: ${item}`)); diff --git a/test/package-smoke.ts b/test/package-smoke.ts index af05a27..c3f41a0 100644 --- a/test/package-smoke.ts +++ b/test/package-smoke.ts @@ -14,7 +14,7 @@ try { const metadata = JSON.parse(readFileSync('package.json', 'utf8')); assert.equal(metadata.private, false); assert.deepEqual(metadata.publishConfig, { registry: 'https://registry.npmjs.org/', access: 'public', tag: 'latest' }); - assert.deepEqual(metadata.dependencies, { 'fs-ext-extra-prebuilt': '2.2.14', 'smol-toml': '1.9.0' }); + assert.deepEqual(metadata.dependencies, { 'fs-ext-extra-prebuilt': '2.2.14', 'smol-toml': '1.9.1' }); for (const hook of ['preinstall', 'install', 'postinstall', 'prepare', 'prepack']) { assert.equal(metadata.scripts[hook], undefined); } diff --git a/test/release.test.ts b/test/release.test.ts index 9aaf2d9..b2f8cba 100644 --- a/test/release.test.ts +++ b/test/release.test.ts @@ -49,13 +49,13 @@ test('release gate rejects moved main, absent/failed/pending CI and missing envi assert.throws(() => validateGitHubGate(sha, main, runs, { name: 'npm', protection_rules: [{ type: 'required_reviewers', reviewers: [] }] })); }); -test('0.5.0 stable staging requires verified 0.4.0 and unchanged preview/tag baselines', () => { +test('0.5.1 stable staging requires verified 0.5.0 and unchanged preview/tag baselines', () => { const previewDist = { integrity: 'sha512-h4SMvrQ/LWA9osd4EHIs9rSTqv1u+S3MXQAmn+yG/gZ9+7NwYMutq+Oa5K/0ggIq61Wfwdh11Cv+5YdEffiNMA==', attestations: { url: 'https://registry.npmjs.org/-/npm/v1/attestations/session-peer@0.1.0-preview.1' } }; - const stableDist = { integrity: 'sha512-mMNJ118atoflhpFTkf2+DtO7MRf7FQnPK3lKWbq/vd8Xvb5DsjUtAgydIBOcd3Xj9BCYGqM50aFhzUr4dDGHSw==', - attestations: { url: 'https://registry.npmjs.org/-/npm/v1/attestations/session-peer@0.4.0' } }; - const current = { name: pkg.name, versions: { '0.4.0': { dist: stableDist }, '0.1.0-preview.1': { dist: previewDist } }, - 'dist-tags': { latest: '0.4.0', preview: '0.1.0-preview.1' } }; + const stableDist = { integrity: 'sha512-lLc7SFRUydY5TjcX8aE18kgfC88Wi4Mx/OXcbLlk0W9yH+JYLkxGwmZIiC7S1bczFgXzsP3xSe8DqiD7YplpDw==', + attestations: { url: 'https://registry.npmjs.org/-/npm/v1/attestations/session-peer@0.5.0' } }; + const current = { name: pkg.name, versions: { '0.5.0': { dist: stableDist }, '0.1.0-preview.1': { dist: previewDist } }, + 'dist-tags': { latest: '0.5.0', preview: '0.1.0-preview.1' } }; validateRegistryState(pkg, 'stable-stage', current); assert.throws(() => validateRegistryState(pkg, 'stable-stage', null)); assert.throws(() => validateRegistryState(pkg, 'trusted-stage', current)); @@ -71,10 +71,10 @@ test('0.5.0 stable staging requires verified 0.4.0 and unchanged preview/tag bas versions: { ...current.versions, '0.1.0-preview.1': { dist: { ...previewDist, attestations: undefined } } } })); const preview = { ...pkg, version: '0.1.0-preview.2', publishConfig: { ...pkg.publishConfig, tag: 'preview' } }; assert.throws(() => validateRegistryState(preview, 'trusted-stage', { ...current, 'dist-tags': { latest: '0.1.0-preview.0' } }), /latest_points_to_prerelease/); - assert.throws(() => validateRegistryState(pkg, 'stable-stage', { ...current, versions: { ...current.versions, '0.4.0': { dist: { ...stableDist, integrity: 'tampered' } } } }), /verified_stable_changed/); - assert.throws(() => validateRegistryState(pkg, 'stable-stage', { ...current, versions: { ...current.versions, '0.4.0': { dist: { ...stableDist, attestations: undefined } } } })); + assert.throws(() => validateRegistryState(pkg, 'stable-stage', { ...current, versions: { ...current.versions, '0.5.0': { dist: { ...stableDist, integrity: 'tampered' } } } }), /verified_stable_changed/); + assert.throws(() => validateRegistryState(pkg, 'stable-stage', { ...current, versions: { ...current.versions, '0.5.0': { dist: { ...stableDist, attestations: undefined } } } })); validateRegistryState(preview, 'trusted-stage', { ...current, - 'dist-tags': { latest: '0.4.0', preview: '0.1.0-preview.1' } }); + 'dist-tags': { latest: '0.5.0', preview: '0.1.0-preview.1' } }); }); test('release artifact binds version, allowlist, commit, SHA-256 and registry integrity', () => { diff --git a/test/replies.test.ts b/test/replies.test.ts index 3046f6f..bedc2f4 100644 --- a/test/replies.test.ts +++ b/test/replies.test.ts @@ -141,7 +141,7 @@ if(args.at(-1)==='exit 0'){const mode=process.env.FAKE_PROBE; if(mode==='timeout'){console.error('ssh: connect to host origin port 22: Operation timed out SECRET-SENTINEL');process.exit(255);} console.error('SECRET-SENTINEL');process.exit(1);} if((process.env.FAKE_DOWN||'').split(',').includes(dest)){console.error('Connection refused');process.exit(255);} -if(args.at(-1).endsWith('--version')){console.log('session-peer 0.5.0 (typescript)');process.exit(0);} +if(args.at(-1).endsWith('--version')){console.log('session-peer 0.5.1 (typescript)');process.exit(0);} const r=spawnSync(${JSON.stringify(process.execPath)},[${JSON.stringify(cli)},'--stdio-request'],{input,encoding:'utf8',env:{...process.env,SESSION_PEER_TAILSCALE:'off'}}); process.stdout.write(r.stdout);process.exit(r.status);`, { mode: 0o700 }); writeFileSync(join(path, 'tailscale'), `#!${process.execPath} diff --git a/test/reply-agent-binding.test.ts b/test/reply-agent-binding.test.ts index 5201985..31162fd 100644 --- a/test/reply-agent-binding.test.ts +++ b/test/reply-agent-binding.test.ts @@ -45,7 +45,7 @@ function fixture(t: TestContext) { const fs=require('node:fs'),{spawnSync}=require('node:child_process'); fs.appendFileSync(${JSON.stringify(sshLog)},'1'); if(process.argv.includes('-G')){console.log('user fixture-user');process.exit(0);} -if(process.argv.at(-1).endsWith('--version')){console.log('session-peer 0.5.0 (typescript)');process.exit(0);} +if(process.argv.at(-1).endsWith('--version')){console.log('session-peer 0.5.1 (typescript)');process.exit(0);} const input=fs.readFileSync(0,'utf8'); const result=spawnSync(${JSON.stringify(process.execPath)},[${JSON.stringify(cli)},'--stdio-request'],{input,encoding:'utf8',env:process.env}); process.stdout.write(result.stdout);process.exit(result.status);`, { mode: 0o700 }); diff --git a/test/skill-guidance.test.ts b/test/skill-guidance.test.ts index a0fdf21..750103b 100644 --- a/test/skill-guidance.test.ts +++ b/test/skill-guidance.test.ts @@ -41,9 +41,10 @@ test('published skill 0.2.1 complete bytes match the recorded release and finite assert.deepEqual(validateSkillMetadata(text, '0.3.6'), expected); assert.deepEqual(validateSkillMetadata(text, '0.4.0'), expected); assert.deepEqual(validateSkillMetadata(text, '0.5.0'), expected); + assert.deepEqual(validateSkillMetadata(text, '0.5.1'), expected); const path = fileURLToPath(url); assert.deepEqual(inspectSkill(path), { ...expected, path }); - for (const runtime of ['0.3.2', '0.3.3', '0.3.4', '0.3.7', '0.4.1', '0.5.1', '0.6.0']) { + for (const runtime of ['0.3.2', '0.3.3', '0.3.4', '0.3.7', '0.4.1', '0.5.2', '0.6.0']) { assert.deepEqual(validateSkillMetadata(text, runtime), { status: 'incompatible', code: 'skill_contract_mismatch', verification: 'metadata_only' }); diff --git a/test/skill-metadata.test.ts b/test/skill-metadata.test.ts index 2c49e8a..1ecd45f 100644 --- a/test/skill-metadata.test.ts +++ b/test/skill-metadata.test.ts @@ -29,7 +29,7 @@ test('finite runtime/profile matrix preserves published 0.3.2 and 0.3.3 profiles const legacy = fixtureText(), successor = fixtureText({ ...fields, version: '0.2.0', 'runtime-full-version': '0.3.2' }); assert.deepEqual(validateSkillMetadata(legacy, '0.3.2'), compatible); assert.deepEqual(validateSkillMetadata(successor, '0.3.2'), incompatible); - assert.equal(VERSION, '0.5.0'); + assert.equal(VERSION, '0.5.1'); for (const text of [legacy, successor]) { assert.deepEqual(validateSkillMetadata(text, '0.3.3'), compatible); assert.deepEqual(validateSkillMetadata(text), compatible); @@ -43,7 +43,7 @@ test('finite runtime/profile matrix preserves published 0.3.2 and 0.3.3 profiles test('0.3.4 patch row permits only the two exact published profiles', () => { const successor = { ...fields, version: '0.2.0', 'runtime-full-version': '0.3.2' }; - assert.equal(VERSION, '0.5.0'); + assert.equal(VERSION, '0.5.1'); for (const values of [fields, successor]) { assert.deepEqual(validateSkillMetadata(fixtureText(values), '0.3.4'), compatible); for (const key of Object.keys(values)) { @@ -63,7 +63,7 @@ test('0.3.6 release candidate carries forward only the finite 0.3.5 profiles', t const { file } = fixture(t); const successor = { ...fields, version: '0.2.1', 'runtime-full-version': '0.3.2' }; const text = fixtureText(successor); - assert.equal(VERSION, '0.5.0'); + assert.equal(VERSION, '0.5.1'); for (const values of [fields, { ...fields, version: '0.2.0', 'runtime-full-version': '0.3.2' }, successor]) { const profile = fixtureText(values); assert.deepEqual(validateSkillMetadata(profile, '0.3.5'), compatible); @@ -80,12 +80,12 @@ test('0.3.6 release candidate carries forward only the finite 0.3.5 profiles', t assert.deepEqual(validateSkillMetadata(text), compatible); assert.deepEqual(validateSkillMetadata(text, undefined), compatible); writeFileSync(file, text); - // The candidate doctor uses compiled 0.5.0; explicit historical rows remain unchanged. + // The candidate doctor uses compiled 0.5.1; explicit historical rows remain unchanged. assert.deepEqual(inspectSkill(file), { path: file, ...compatible }); for (const runtime of [null, {}, 0, '', '0.3.5-preview.1', '00.3.5', '0.3.6-preview.1', '00.3.6']) { assert.deepEqual(validateSkillMetadata(text, runtime), missing); } - for (const runtime of ['0.3.5', '0.3.6', '0.4.0', '0.5.0']) { + for (const runtime of ['0.3.5', '0.3.6', '0.4.0', '0.5.0', '0.5.1']) { for (const key of Object.keys(successor)) { assert.deepEqual(validateSkillMetadata(fixtureText({ ...successor, [key]: 'unsupported' }), runtime), incompatible, key); const omitted = { ...successor } as Record; delete omitted[key]; @@ -385,7 +385,7 @@ test('complete companion 0.2.1 candidate bytes match the exact reviewed fixture assert.equal(createHash('sha256').update(bytes).digest('hex'), '96d5a0d049f8a4d193703af3583b198a91079d44ae373de34e94a54e2296a4d6'); const text = new TextDecoder('utf-8', { fatal: true }).decode(bytes); - assert.equal(VERSION, '0.5.0'); + assert.equal(VERSION, '0.5.1'); assert.deepEqual(validateSkillMetadata(text), compatible); assert.deepEqual(validateSkillMetadata(text, undefined), compatible); assert.deepEqual(validateSkillMetadata(text, '0.3.5'), compatible); @@ -399,18 +399,19 @@ test('complete companion 0.2.1 candidate bytes match the exact reviewed fixture }); -test('0.5.0 finite row preserves the four exact 0.4.0 profiles', t => { +test('0.5.1 finite row preserves the four exact 0.4.0 and 0.5.0 profiles', t => { const {file}=fixture(t); - assert.equal(VERSION,'0.5.0'); + assert.equal(VERSION,'0.5.1'); for(const values of [fields,{...fields,version:'0.2.0','runtime-full-version':'0.3.2'}, {...fields,version:'0.2.1','runtime-full-version':'0.3.2'},{...fields,version:'0.3.0','runtime-full-version':'0.4.0'}]){ const text=fixtureText(values); assert.deepEqual(validateSkillMetadata(text,'0.4.0'),compatible); assert.deepEqual(validateSkillMetadata(text,'0.5.0'),compatible); + assert.deepEqual(validateSkillMetadata(text,'0.5.1'),compatible); assert.deepEqual(validateSkillMetadata(text),compatible); assert.deepEqual(validateSkillMetadata(text,undefined),compatible); writeFileSync(file,text);assert.deepEqual(inspectSkill(file),{path:file,...compatible}); - for(const runtime of ['0.3.7','0.4.1','0.5.1','0.6.0'])assert.deepEqual(validateSkillMetadata(text,runtime),incompatible); - for(const runtime of [null,{},0,'','0.4.0-preview.1','00.4.0','0.5.0-preview.1','00.5.0'])assert.deepEqual(validateSkillMetadata(text,runtime),missing); + for(const runtime of ['0.3.7','0.4.1','0.5.2','0.6.0'])assert.deepEqual(validateSkillMetadata(text,runtime),incompatible); + for(const runtime of [null,{},0,'','0.4.0-preview.1','00.4.0','0.5.0-preview.1','00.5.0','0.5.1-preview.1','00.5.1'])assert.deepEqual(validateSkillMetadata(text,runtime),missing); for(const key of Object.keys(values)){ assert.deepEqual(validateSkillMetadata(fixtureText({...values,[key]:'unsupported'})),incompatible,key); const omitted={...values} as Record;delete omitted[key];assert.deepEqual(validateSkillMetadata(fixtureText(omitted)),missing,key); @@ -433,17 +434,18 @@ test('0.5.0 finite row preserves the four exact 0.4.0 profiles', t => { // Companion PR37 b8ff909 is an immutable review checkpoint, not an install pin. -test('complete 0.3.0 companion candidate bytes match exact review and the 0.4.0 and 0.5.0 guards', t=>{ +test('complete 0.3.0 companion candidate bytes match exact review and the 0.4.0, 0.5.0 and 0.5.1 guards', t=>{ const bytes=readFileSync(new URL('./fixtures/ts-skill-0.3.0-candidate.md',import.meta.url)); assert.equal(bytes.length,20162);assert.ok(bytes.length<=SKILL_METADATA_LIMIT); assert.equal(createHash('sha256').update(bytes).digest('hex'),'1ae3690cb5d03777bf448cf06496fed27e4a9329deb03741d6d956a3b13c5ed4'); - const text=new TextDecoder('utf-8',{fatal:true}).decode(bytes);assert.equal(VERSION,'0.5.0'); + const text=new TextDecoder('utf-8',{fatal:true}).decode(bytes);assert.equal(VERSION,'0.5.1'); assert.deepEqual(validateSkillMetadata(text),compatible); assert.deepEqual(validateSkillMetadata(text,undefined),compatible); assert.deepEqual(validateSkillMetadata(text,'0.4.0'),compatible); assert.deepEqual(validateSkillMetadata(text,'0.5.0'),compatible); + assert.deepEqual(validateSkillMetadata(text,'0.5.1'),compatible); const {file}=fixture(t);writeFileSync(file,bytes);assert.deepEqual(inspectSkill(file),{path:file,...compatible}); - for(const runtime of ['0.3.2','0.3.3','0.3.4','0.3.5','0.3.6','0.3.7','0.4.1','0.5.1','0.6.0'])assert.deepEqual(validateSkillMetadata(text,runtime),incompatible); - for(const runtime of [null,{},0,'','0.4.0-preview.1','00.4.0','0.5.0-preview.1','00.5.0'])assert.deepEqual(validateSkillMetadata(text,runtime),missing); + for(const runtime of ['0.3.2','0.3.3','0.3.4','0.3.5','0.3.6','0.3.7','0.4.1','0.5.2','0.6.0'])assert.deepEqual(validateSkillMetadata(text,runtime),incompatible); + for(const runtime of [null,{},0,'','0.4.0-preview.1','00.4.0','0.5.0-preview.1','00.5.0','0.5.1-preview.1','00.5.1'])assert.deepEqual(validateSkillMetadata(text,runtime),missing); // Only bounded metadata is interpreted; candidate body instructions stay inert. }); diff --git a/test/ssh-hosts.test.ts b/test/ssh-hosts.test.ts index 2429fd2..e804781 100644 --- a/test/ssh-hosts.test.ts +++ b/test/ssh-hosts.test.ts @@ -32,7 +32,7 @@ fs.appendFileSync(${JSON.stringify(log)},JSON.stringify({args,input})+'\\n'); const dest=args[args.indexOf('--')+1],list=k=>(process.env[k]||'').split(','); if(args.includes('-G')){if(process.env.FAKE_G==='fail'||list('FAKE_G_FAIL').includes(dest))process.exit(255);console.log('hostname x\\nuser config-user\\nport 22');process.exit(0);} if(list('FAKE_DOWN').includes(dest)){console.error('ssh: connect to host: Connection refused SECRET-SENTINEL');process.exit(255);} -if(args.at(-1).endsWith('--version')){console.log('session-peer 0.5.0 (typescript)');process.exit(0);} +if(args.at(-1).endsWith('--version')){console.log('session-peer 0.5.1 (typescript)');process.exit(0);} if(list('FAKE_LOSS').includes(dest))process.exit(255); const r=spawnSync(${JSON.stringify(process.execPath)},[${JSON.stringify(cli)},'--stdio-request'],{input,encoding:'utf8',env:process.env}); if(list('FAKE_MALFORMED_TARGET').includes(dest)){const value=JSON.parse(r.stdout);value.target.agent={toString:null};r.stdout=JSON.stringify(value);} @@ -339,7 +339,7 @@ const proxy=args.find(a=>a.startsWith('ProxyCommand=')); if(proxy){const cmd=proxy.slice(13).replace(/%(.)/g,(m,c)=>c==='%'?'%':c==='h'?name:c==='p'?port:c==='n'?dest:c==='r'?'config-user':m); if(spawnSync('/bin/sh',['-c','exec '+cmd],{stdio:'inherit'}).status!==0)process.exit(255);} fs.appendFileSync(${JSON.stringify(log)},JSON.stringify({hop:false,args,input})+'\\n'); -if(args.at(-1).endsWith('--version')){console.log('session-peer 0.5.0 (typescript)');process.exit(0);} +if(args.at(-1).endsWith('--version')){console.log('session-peer 0.5.1 (typescript)');process.exit(0);} const r=spawnSync(${JSON.stringify(process.execPath)},[${JSON.stringify(cli)},'--stdio-request'],{input,encoding:'utf8',env:process.env}); process.stdout.write(r.stdout);process.exit(r.status);`, { mode: 0o700 }); const env = { PATH: bin + delimiter + f.env.PATH }; diff --git a/test/transport.test.ts b/test/transport.test.ts index f2021aa..c80abcc 100644 --- a/test/transport.test.ts +++ b/test/transport.test.ts @@ -171,20 +171,20 @@ test('SSH preflight reports allowlisted causes without leaking stderr or sending test('SSH request framing: no message in remote command; response loss never retries', async t => { const { path, env } = setup(t), messages = await inbox(t, path); const ssh = join(path, 'ssh'); - writeFileSync(ssh, `#!${process.execPath}\n${sshConfigUser}const {spawnSync}=require('node:child_process'); const a=process.argv.slice(2);if(a.at(-1).endsWith('--version')){console.log('session-peer 0.5.0 (typescript)');process.exit(0);}if(a.at(-1).includes('SECRET-SENTINEL'))process.exit(99);const input=require('node:fs').readFileSync(0,'utf8');const r=spawnSync(${JSON.stringify(process.execPath)},[${JSON.stringify(cli)},'--stdio-request'],{input,encoding:'utf8',env:process.env});process.stdout.write(r.stdout);process.exit(r.status);`, { mode: 0o700 }); + writeFileSync(ssh, `#!${process.execPath}\n${sshConfigUser}const {spawnSync}=require('node:child_process'); const a=process.argv.slice(2);if(a.at(-1).endsWith('--version')){console.log('session-peer 0.5.1 (typescript)');process.exit(0);}if(a.at(-1).includes('SECRET-SENTINEL'))process.exit(99);const input=require('node:fs').readFileSync(0,'utf8');const r=spawnSync(${JSON.stringify(process.execPath)},[${JSON.stringify(cli)},'--stdio-request'],{input,encoding:'utf8',env:process.env});process.stdout.write(r.stdout);process.exit(r.status);`, { mode: 0o700 }); const result = await invoke(['send', '--host', 'fixture', '--to', String(process.pid), '--message', 'SECRET-SENTINEL', '--no-from'], { ...env, PATH: path + delimiter + env.PATH }); assert.equal(result.status, 'posted'); assert.equal(messages.length, 1); // Valid JSON mixed with banner/trailing output is not a verified response. for (const command of ['send', 'list']) for (const prefix of [true, false]) { const response = JSON.stringify({ schemaVersion: 1, command, host: 'fixture', ok: true, status: 'posted', submitted: true, consumptionConfirmed: false }); const stdout = prefix ? 'untrusted banner\n' + response : response + '\ntrailing junk'; - writeFileSync(ssh, `#!${process.execPath}\n${sshConfigUser}if(process.argv.at(-1).endsWith('--version'))console.log('session-peer 0.5.0 (typescript)');else console.log(${JSON.stringify(stdout)});`, { mode: 0o700 }); + writeFileSync(ssh, `#!${process.execPath}\n${sshConfigUser}if(process.argv.at(-1).endsWith('--version'))console.log('session-peer 0.5.1 (typescript)');else console.log(${JSON.stringify(stdout)});`, { mode: 0o700 }); const unverified = await invoke([command, '--host', 'fixture', ...(command === 'send' ? ['--to', 'fixture', '--message', 'not-provable'] : [])], { ...env, PATH: path + delimiter + env.PATH }); assert.equal(unverified.status, command === 'send' ? 'unknown' : 'refused'); assert.equal(unverified.submitted, command === 'send' ? null : false); assert.equal(unverified.retryAllowed, false); } - writeFileSync(ssh, `#!${process.execPath}\n${sshConfigUser}if(process.argv.at(-1).endsWith('--version'))console.log('session-peer 0.5.0 (typescript)');else process.exit(255);`, { mode: 0o700 }); + writeFileSync(ssh, `#!${process.execPath}\n${sshConfigUser}if(process.argv.at(-1).endsWith('--version'))console.log('session-peer 0.5.1 (typescript)');else process.exit(255);`, { mode: 0o700 }); const lost = await invoke(['send', '--host', 'fixture', '--to', 'fixture', '--message', 'lost'], { ...env, PATH: path + delimiter + env.PATH }); assert.equal(lost.status, 'unknown'); assert.equal(lost.retryAllowed, false); assert.equal(messages.length, 1); }); @@ -333,7 +333,7 @@ test('a saved inactive home needs opt-in and still rejects relocated or unreadab test('SSH preflight accepts a verified version despite noisy stderr and propagates remote exit codes', async t => { const { path, env } = setup(t), messages = await inbox(t, path); const ssh = join(path, 'ssh'); - writeFileSync(ssh, `#!${process.execPath}\n${sshConfigUser}const {spawnSync}=require('node:child_process');if(process.argv.at(-1).endsWith('--version')){console.error('curl: (7) Failed to connect: Connection refused');console.log('session-peer 0.5.0 (typescript)');process.exit(0);}const r=spawnSync(${JSON.stringify(process.execPath)},[${JSON.stringify(cli)},'--stdio-request'],{input:require('node:fs').readFileSync(0,'utf8'),encoding:'utf8',env:process.env});process.stdout.write(r.stdout);process.exit(r.status);`, { mode: 0o700 }); + writeFileSync(ssh, `#!${process.execPath}\n${sshConfigUser}const {spawnSync}=require('node:child_process');if(process.argv.at(-1).endsWith('--version')){console.error('curl: (7) Failed to connect: Connection refused');console.log('session-peer 0.5.1 (typescript)');process.exit(0);}const r=spawnSync(${JSON.stringify(process.execPath)},[${JSON.stringify(cli)},'--stdio-request'],{input:require('node:fs').readFileSync(0,'utf8'),encoding:'utf8',env:process.env});process.stdout.write(r.stdout);process.exit(r.status);`, { mode: 0o700 }); const remoteEnv = { ...env, PATH: path + delimiter + env.PATH }; // #55 + #49/#51 over SSH: a flag-like body travels as --message= and posts literally. const sent = await invoke(['send', '--host', 'fixture', '--to', String(process.pid), '--message=--dry-run', '--no-from'], remoteEnv); @@ -374,6 +374,41 @@ test('ordinary SSH results cannot opt themselves into handoff or claim unsolicit // Fixture values are data, never part of the executable JavaScript source. const config={log,handoff:{...h,targetGeneration:'\');throw new Error("fixture data executed");//\u2028\u2029'}}; writeFileSync(fake+'.json',JSON.stringify(config),{mode:0o600}); - writeFileSync(fake,`#!${process.execPath}\n${sshConfigUser}if(process.argv.at(-1).includes('--version')){console.log('session-peer 0.5.0 (typescript)');process.exit(0);}const fs=require('node:fs');const config=JSON.parse(fs.readFileSync(__filename+'.json','utf8'));let input='';process.stdin.on('data',p=>input+=p);process.stdin.on('end',()=>{fs.appendFileSync(config.log,'1');console.log(JSON.stringify({schemaVersion:1,command:'send',host:'fixture',ok:true,status:'posted',submitted:true,consumptionConfirmed:false,dryRun:false,target:{agent:'claude',pid:123,name:null},remoteMarker:'kept',handoff:config.handoff,handoffQuery:{status:'unknown'},handoffWarning:'fixture-only'}));});`,{mode:0o700}); + writeFileSync(fake,`#!${process.execPath}\n${sshConfigUser}if(process.argv.at(-1).includes('--version')){console.log('session-peer 0.5.1 (typescript)');process.exit(0);}const fs=require('node:fs');const config=JSON.parse(fs.readFileSync(__filename+'.json','utf8'));let input='';process.stdin.on('data',p=>input+=p);process.stdin.on('end',()=>{fs.appendFileSync(config.log,'1');console.log(JSON.stringify({schemaVersion:1,command:'send',host:'fixture',ok:true,status:'posted',submitted:true,consumptionConfirmed:false,dryRun:false,target:{agent:'claude',pid:123,name:null},remoteMarker:'kept',handoff:config.handoff,handoffQuery:{status:'unknown'},handoffWarning:'fixture-only'}));});`,{mode:0o700}); const r=await invoke(['send','--host','fixture','--to','123','--message','fixture','--no-from','--no-reply-to'],{...env,PATH:path+delimiter+(env.PATH??'')});assert.equal(r.code,0);assert.equal(r.status,'posted');assert.equal(r.submitted,true);assert.equal(r.target.name,null);assert.equal(r.remoteMarker,'kept');assert.equal('handoff' in r,false);assert.equal('handoffQuery' in r,false);assert.equal('handoffWarning' in r,false);assert.equal(readFileSync(log,'utf8'),'1'); }); + + +test('malformed exponent configuration refuses Codex dry-run and send before the fixture queue', async t => { + const { path, env } = setup(t), home = join(path, 'home'); database(home); await holder(t, path, home); + const queue = join(path, 'queue'), count = join(path, 'count'), config = join(home, 'config.toml'); + writeFileSync(count, ''); + writeFileSync(queue, `#!${process.execPath}\nrequire('node:fs').appendFileSync(${JSON.stringify(count)},'1'); console.log('Queued message toml-051 for thread '+process.argv[4]+'.');`, { mode: 0o700 }); + const args = ['send', '--to', `codex:${id}`, '--codex-home', home, '--codex-bin', queue, '--message', 'fixture', '--no-from', '--no-reply-to']; + // Valid single exponent signs remain acceptable; dry-run never invokes queue. + for (const number of ['3e+20', '3e-20']) { + writeFileSync(config, `numeric_setting = ${number}\n[profiles.p]\nweight = ${number}\n`); + const result = await invoke([...args, '--dry-run'], env); + assert.equal(result.status, 'validated', number); assert.equal(result.submitted, false); + assert.equal(readFileSync(count, 'utf8'), ''); + } + for (const number of ['3e++20', '3e--20']) { + for (const text of [`numeric_setting = ${number}\n`, `[profiles.p]\nweight = ${number}\n`]) { + writeFileSync(config, text); + for (const extra of [['--dry-run'], []]) { + const result = await invoke([...args, ...extra], env); + assert.equal(result.error, 'codex_config_unreadable', text); + assert.equal(result.status, 'refused'); assert.equal(result.submitted, false); + assert.equal(result.code, 1); assert.equal(readFileSync(count, 'utf8'), ''); + } + } + } + // This parser correction does not add support for an explicit root override. + writeFileSync(config, 'numeric_setting = 3e+20\nsqlite_home = "/fixture/relocated"\n'); + for (const extra of [['--dry-run'], []]) { + const result = await invoke([...args, ...extra], env); + assert.equal(result.error, 'unsupported_codex_sqlite_home'); + assert.equal(result.status, 'refused'); assert.equal(result.submitted, false); + assert.equal(readFileSync(count, 'utf8'), ''); + } +}); diff --git a/test/updates.test.ts b/test/updates.test.ts index d541f03..6eaaa46 100644 --- a/test/updates.test.ts +++ b/test/updates.test.ts @@ -16,7 +16,7 @@ import { acquireLock, cacheDirectory, checkUpdate, compareVersions, manager, ref const cli = resolve('dist/cli.js'); const SENTINEL = 'REGISTRY-BODY-SENTINEL'; type Reply = { status?: number; body?: string; wait?: number }; -async function fixture(t: TestContext, reply: Reply = { body: JSON.stringify({ latest: '0.5.1', preview: '0.6.0-preview.1' }) }) { +async function fixture(t: TestContext, reply: Reply = { body: JSON.stringify({ latest: '0.5.2', preview: '0.6.0-preview.1' }) }) { const root = mkdtempSync(join(process.env.TASK_TEMP ?? tmpdir(), 'codex-updates-')); mkdirSync(join(root, '.claude/sessions'), { recursive: true }); const requests: string[] = []; @@ -82,11 +82,11 @@ test('package-manager guidance requires a positively identified owner; uncertain read: path => { if (!(path in files)) throw new Error('ENOENT'); return files[path]!; }, real: path => { if (path in links) return links[path]!; if (path in files) return path; throw new Error('ENOENT'); } }); - const declared = JSON.stringify({ dependencies: { 'session-peer': '0.5.0' } }); + const declared = JSON.stringify({ dependencies: { 'session-peer': '0.5.1' } }); const posixGlobal = (prefix: string) => probe({ [`${prefix}/lib/node_modules/session-peer/dist/cli.js`]: '' }, { [`${prefix}/bin/session-peer`]: `${prefix}/lib/node_modules/session-peer/dist/cli.js` }); const windowsGlobal = (prefix: string) => probe({ [`${prefix}/session-peer.cmd`]: '@ECHO off\r\nnode "%dp0%\\node_modules\\session-peer\\dist\\cli.js" %*\r\n' }); - const cmd = { npm: 'npm install --global --ignore-scripts session-peer@0.5.1' }; + const cmd = { npm: 'npm install --global --ignore-scripts session-peer@0.5.2' }; const cases: [string, NodeJS.Platform, Probe, string, string | null][] = [ ['/usr/local/lib/node_modules/session-peer/dist/updates.js', 'linux', posixGlobal('/usr/local'), 'npm', cmd.npm], ['/home/u/.nvm/versions/node/v24.16.0/lib/node_modules/session-peer/dist/updates.js', 'darwin', posixGlobal('/home/u/.nvm/versions/node/v24.16.0'), 'npm', cmd.npm], @@ -100,27 +100,27 @@ test('package-manager guidance requires a positively identified owner; uncertain ['/home/u/.nvm/versions/node/v24.16.0/lib/node_modules/session-peer/dist/updates.js', 'linux', probe({}), 'unknown', null], ['/home/u/.local/lib/node_modules/session-peer/dist/updates.js', 'linux', probe({ '/home/u/.local/lib/node_modules/session-peer/dist/cli.js': '' }, { '/home/u/.local/bin/session-peer': '/home/u/.local/share/pipx/venvs/session-peer/bin/session-peer' }), 'unknown', null], - ['/home/u/.volta/tools/image/packages/session-peer/lib/node_modules/session-peer/dist/updates.js', 'linux', probe({}), 'volta', 'volta install session-peer@0.5.1'], - ['C:/Users/u/AppData/Local/Volta/tools/image/packages/session-peer/node_modules/session-peer/dist/updates.js', 'win32', probe({}), 'volta', 'volta install session-peer@0.5.1'], + ['/home/u/.volta/tools/image/packages/session-peer/lib/node_modules/session-peer/dist/updates.js', 'linux', probe({}), 'volta', 'volta install session-peer@0.5.2'], + ['C:/Users/u/AppData/Local/Volta/tools/image/packages/session-peer/node_modules/session-peer/dist/updates.js', 'win32', probe({}), 'volta', 'volta install session-peer@0.5.2'], ['C:\\work\\app\\node_modules\\session-peer\\dist\\updates.js', 'win32', probe({ 'C:/work/app/package.json': declared }), 'npm_project', null], ['/work/app/node_modules/session-peer/dist/updates.js', 'linux', probe({ '/work/app/package.json': declared }), 'npm_project', null], ['/work/app/node_modules/session-peer/dist/updates.js', 'linux', probe({ '/work/app/package.json': JSON.stringify({ dependencies: { other: '1' } }) }), 'unknown', null], ['/work/app/node_modules/session-peer/dist/updates.js', 'linux', probe({}), 'unknown', null], - ['/home/u/.local/share/pnpm/global/5/node_modules/.pnpm/session-peer@0.5.0/node_modules/session-peer/dist/updates.js', 'linux', - probe({ '/home/u/.local/share/pnpm/global/5/package.json': declared }), 'pnpm', 'pnpm add --global --ignore-scripts session-peer@0.5.1'], - ['/work/app/node_modules/.pnpm/session-peer@0.5.0/node_modules/session-peer/dist/updates.js', 'linux', probe({ '/work/app/package.json': declared }), 'pnpm_project', null], - ['/work/app/node_modules/.pnpm/session-peer@0.5.0/node_modules/session-peer/dist/updates.js', 'linux', probe({}), 'unknown', null], - ['/home/u/.config/yarn/global/node_modules/session-peer/dist/updates.js', 'linux', probe({ '/home/u/.config/yarn/global/package.json': declared }), 'yarn', 'yarn global add --ignore-scripts session-peer@0.5.1'], + ['/home/u/.local/share/pnpm/global/5/node_modules/.pnpm/session-peer@0.5.1/node_modules/session-peer/dist/updates.js', 'linux', + probe({ '/home/u/.local/share/pnpm/global/5/package.json': declared }), 'pnpm', 'pnpm add --global --ignore-scripts session-peer@0.5.2'], + ['/work/app/node_modules/.pnpm/session-peer@0.5.1/node_modules/session-peer/dist/updates.js', 'linux', probe({ '/work/app/package.json': declared }), 'pnpm_project', null], + ['/work/app/node_modules/.pnpm/session-peer@0.5.1/node_modules/session-peer/dist/updates.js', 'linux', probe({}), 'unknown', null], + ['/home/u/.config/yarn/global/node_modules/session-peer/dist/updates.js', 'linux', probe({ '/home/u/.config/yarn/global/package.json': declared }), 'yarn', 'yarn global add --ignore-scripts session-peer@0.5.2'], ['/home/u/.config/yarn/global/node_modules/session-peer/dist/updates.js', 'linux', probe({}), 'unknown', null], - ['/home/u/.bun/install/global/node_modules/session-peer/dist/updates.js', 'darwin', probe({ '/home/u/.bun/install/global/package.json': declared }), 'bun', 'bun add --global --ignore-scripts session-peer@0.5.1'], - ['/home/u/.npm/_npx/abc123/node_modules/session-peer/dist/updates.js', 'linux', probe({}), 'npx', 'npx --yes --ignore-scripts session-peer@0.5.1 --version'], + ['/home/u/.bun/install/global/node_modules/session-peer/dist/updates.js', 'darwin', probe({ '/home/u/.bun/install/global/package.json': declared }), 'bun', 'bun add --global --ignore-scripts session-peer@0.5.2'], + ['/home/u/.npm/_npx/abc123/node_modules/session-peer/dist/updates.js', 'linux', probe({}), 'npx', 'npx --yes --ignore-scripts session-peer@0.5.2 --version'], ['/src/session-peer-ts/dist/updates.js', 'linux', probe({ '/src/session-peer-ts/package.json': JSON.stringify({ name: 'session-peer' }) }), 'source', null], ['/src/other/dist/updates.js', 'linux', probe({}), 'unknown', null], ['/opt/unrelated/node_modules/other/updates.js', 'linux', probe({}), 'unknown', null], ]; for (const [path, platform, files, owner, command] of cases) { assert.equal(manager(path, platform, files), owner, `${platform} ${path}`); - assert.equal(upgradeCommand(owner as Manager, '0.5.1'), command, path); + assert.equal(upgradeCommand(owner as Manager, '0.5.2'), command, path); if (command === null) assert.ok(upgradeGuidance(owner as Manager).length > 0); } }); @@ -157,7 +157,7 @@ test('update --check reports npm dist-tags, writes a private atomic cache and le assert.deepEqual(f.requests, ['/-/package/session-peer/dist-tags']); const v = r.value; assert.equal(v.command, 'update'); assert.equal(v.ok, true); assert.equal(v.package, 'session-peer'); - assert.equal(v.current, '0.5.0'); assert.equal(v.latest, '0.5.1'); assert.equal(v.channel, 'latest'); assert.equal(v.distTag, 'latest'); + assert.equal(v.current, '0.5.1'); assert.equal(v.latest, '0.5.2'); assert.equal(v.channel, 'latest'); assert.equal(v.distTag, 'latest'); assert.equal(v.source, 'npm_registry'); assert.equal(v.status, 'update_available'); assert.equal(v.outdated, true); assert.equal(v.updated, false); assert.equal(v.referenceVersion, '1.1.0'); assert.equal(v.cache, 'written'); // The test runs from a checkout; installed-path guidance is covered above and in package-smoke. @@ -168,31 +168,31 @@ test('update --check reports npm dist-tags, writes a private atomic cache and le assert.deepEqual([readFileSync(skill), readFileSync(python)], before); const cached = JSON.parse(readFileSync(f.file, 'utf8')); assert.deepEqual(Object.keys(cached).sort(), ['channel', 'checkedAt', 'latest', 'package', 'schemaVersion', 'source']); - assert.equal(cached.latest, '0.5.1'); + assert.equal(cached.latest, '0.5.2'); assert.deepEqual(readdirSync(f.cache), ['npm-update.json']); if (process.platform !== 'win32') { assert.equal(statSync(f.cache).mode & 0o777, 0o700); assert.equal(statSync(f.file).mode & 0o777, 0o600); } const text = await f.call(['update', '--check', '--output-format', 'text']); - assert.equal(text.code, 0); assert.match(text.stdout, /0\.5\.0 \(typescript\): 0\.5\.1 available on npm dist-tag latest/); + assert.equal(text.code, 0); assert.match(text.stdout, /0\.5\.1 \(typescript\): 0\.5\.2 available on npm dist-tag latest/); assert.match(text.stdout, /source checkout/); assert.equal(text.stderr, ''); - f.state.reply = { body: JSON.stringify({ latest: '0.5.0' }) }; + f.state.reply = { body: JSON.stringify({ latest: '0.5.1' }) }; const current = await f.call(['update', '--check', '--json']); assert.equal(current.value.status, 'up_to_date'); assert.equal(current.value.outdated, false); assert.equal(current.value.updateCommand, null); }); test('preview channel accepts prereleases, compares separately and does not replace the stable cache', async t => { const f = await fixture(t); - f.seed('0.5.0'); + f.seed('0.5.1'); const before = readFileSync(f.file, 'utf8'); const ahead = await f.call(['update', '--check', '--channel', 'preview', '--json']); assert.equal(ahead.code, 0); assert.equal(ahead.value.latest, '0.6.0-preview.1'); assert.equal(ahead.value.channel, 'preview'); assert.equal(ahead.value.status, 'update_available'); assert.equal(ahead.value.outdated, true); assert.equal(ahead.value.cache, 'not_applicable'); - f.state.reply = { body: JSON.stringify({ latest: '0.5.0', preview: '0.1.0-preview.1' }) }; + f.state.reply = { body: JSON.stringify({ latest: '0.5.1', preview: '0.1.0-preview.1' }) }; const older = await f.call(['update', '--check', '--channel=preview', '--json']); assert.equal(older.value.status, 'ahead'); assert.equal(older.value.outdated, false); assert.equal(older.value.updateCommand, null); assert.equal(readFileSync(f.file, 'utf8'), before); - f.state.reply = { body: JSON.stringify({ latest: '0.5.0' }) }; + f.state.reply = { body: JSON.stringify({ latest: '0.5.1' }) }; const missing = await f.call(['update', '--check', '--channel', 'preview', '--json']); assert.equal(missing.code, 1); assert.equal(missing.value.error, 'dist_tag_missing'); }); @@ -205,7 +205,7 @@ test('invalid, failed and slow registry responses refuse without printing bodies [{ body: JSON.stringify({ latest: 9, note: SENTINEL }) }, 'registry_response_invalid'], [{ body: `[${JSON.stringify(SENTINEL)}]` }, 'registry_response_invalid'], [{ body: `${SENTINEL} not json` }, 'registry_response_invalid'], - [{ body: JSON.stringify({ latest: '0.5.1', padding: SENTINEL.repeat(4000) }) }, 'registry_response_invalid'], + [{ body: JSON.stringify({ latest: '0.5.2', padding: SENTINEL.repeat(4000) }) }, 'registry_response_invalid'], [{ status: 500, body: SENTINEL }, 'registry_http_error'], [{ status: 302, body: SENTINEL }, 'registry_unreachable'], ]; @@ -217,7 +217,7 @@ test('invalid, failed and slow registry responses refuse without printing bodies const text = await f.call(['update', '--check', '--output-format=text']); assert.match(text.stdout, new RegExp(`Error: ${error}`)); assert.equal(text.stdout.includes(SENTINEL), false); } - f.state.reply = { body: JSON.stringify({ latest: '0.5.1' }), wait: 8000 }; + f.state.reply = { body: JSON.stringify({ latest: '0.5.2' }), wait: 8000 }; const started = Date.now(); const slow = await f.call(['update', '--check', '--json']); assert.equal(slow.value.error, 'registry_timeout'); assert.ok(Date.now() - started < 7000, 'bounded timeout'); @@ -264,7 +264,7 @@ test('update without --check refuses with ownership guidance and no network; opt test('cached notices are off by default, opt-in additive, stderr-only in text and opt-out wins', async t => { const f = await fixture(t); - f.seed('0.5.1'); + f.seed('0.5.2'); const plain = await f.call(['list', '--json']); assert.equal(plain.code, 0); assert.equal('clientUpdate' in plain.value, false); assert.equal(plain.stderr, ''); const on = { SESSION_PEER_UPDATE_NOTICE: '1' }; @@ -273,14 +273,14 @@ test('cached notices are off by default, opt-in additive, stderr-only in text an const { clientUpdate, ...rest } = json.value; assert.deepEqual(rest, plain.value); assert.deepEqual(Object.keys(clientUpdate).sort(), ['channel', 'checkedAt', 'command', 'current', 'guidance', 'latest', 'managedBy', 'schemaVersion', 'source', 'status']); - assert.equal(clientUpdate.status, 'available'); assert.equal(clientUpdate.current, '0.5.0'); assert.equal(clientUpdate.latest, '0.5.1'); + assert.equal(clientUpdate.status, 'available'); assert.equal(clientUpdate.current, '0.5.1'); assert.equal(clientUpdate.latest, '0.5.2'); assert.equal(clientUpdate.source, 'npm_registry_cache'); assert.match(clientUpdate.checkedAt, /^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\dZ$/); const textOff = await f.call(['list', '--output-format', 'text']); const textOn = await f.call(['list', '--output-format', 'text'], { SESSION_PEER_UPDATE_NOTICE: 'yes' }); assert.equal(textOn.stdout, textOff.stdout); assert.equal(textOn.code, textOff.code); - assert.match(textOn.stderr, /^Update available: session-peer 0\.5\.0 -> 0\.5\.1 \(npm dist-tag latest\)\. This is a source checkout; update it with git and rebuild\.\n$/); + assert.match(textOn.stderr, /^Update available: session-peer 0\.5\.1 -> 0\.5\.2 \(npm dist-tag latest\)\. This is a source checkout; update it with git and rebuild\.\n$/); const doctor = await f.call(['doctor', '--json'], on); - assert.equal(doctor.value.clientUpdate.latest, '0.5.1'); assert.equal(doctor.value.capabilities.updateCheck, true); assert.equal(doctor.value.capabilities.selfUpdate, false); + assert.equal(doctor.value.clientUpdate.latest, '0.5.2'); assert.equal(doctor.value.capabilities.updateCheck, true); assert.equal(doctor.value.capabilities.selfUpdate, false); const refusal = await f.call(['send', '--to', 'codex:bad', '--json', 'hi'], on); assert.equal(refusal.code, 2); assert.equal('clientUpdate' in refusal.value, false); for (const [args, extra] of [[['list', '--json', '--no-update-notice'], on], [['list', '--json'], { ...on, SESSION_PEER_NO_UPDATE_NOTICE: 'TRUE' }], @@ -288,7 +288,7 @@ test('cached notices are off by default, opt-in additive, stderr-only in text an const r = await f.call(args, extra); assert.equal(r.code, 0); assert.equal(r.stderr, ''); assert.equal(r.value?.clientUpdate, undefined); } - f.seed('0.5.0'); + f.seed('0.5.1'); assert.equal('clientUpdate' in (await f.call(['list', '--json'], on)).value, false); // Python's own update.json in the same directory is a different release stream. writeFileSync(join(f.cache, 'update.json'), JSON.stringify({ schemaVersion: 1, latest: '9.9.9', checkedAt: Math.floor(Date.now() / 1000) })); @@ -304,26 +304,26 @@ test('missing, expired and invalid caches refresh once in the background without assert.deepEqual(first.value, baseline.value); assert.equal(first.code, baseline.code); assert.equal(first.stderr, ''); await f.settled(1); assert.equal(f.requests.length, 1); - assert.equal(JSON.parse(readFileSync(f.file, 'utf8')).latest, '0.5.1'); + assert.equal(JSON.parse(readFileSync(f.file, 'utf8')).latest, '0.5.2'); if (process.platform !== 'win32') assert.equal(statSync(f.file).mode & 0o777, 0o600); - assert.equal((await f.call(['list', '--json'], on)).value.clientUpdate.latest, '0.5.1'); + assert.equal((await f.call(['list', '--json'], on)).value.clientUpdate.latest, '0.5.2'); assert.equal(f.requests.length, 1, 'fresh cache does not refresh'); - f.seed('0.5.1', 25 * 3600_000); + f.seed('0.5.2', 25 * 3600_000); assert.equal('clientUpdate' in (await f.call(['list', '--json'], on)).value, false, 'expired cache is not shown'); await f.settled(2); assert.equal(f.requests.length, 2); for (const content of ['not json', JSON.stringify({ schemaVersion: 1, package: 'session-peer', source: 'npm_registry', channel: 'latest', latest: '0.3.0-rc.1', checkedAt: Date.now() }), - JSON.stringify({ schemaVersion: 1, package: 'session-peer', source: 'npm_registry', channel: 'latest', latest: '0.5.1', checkedAt: Date.now() + 3600_000 }), 'x'.repeat(5000)]) { + JSON.stringify({ schemaVersion: 1, package: 'session-peer', source: 'npm_registry', channel: 'latest', latest: '0.5.2', checkedAt: Date.now() + 3600_000 }), 'x'.repeat(5000)]) { const count: number = f.requests.length; writeFileSync(f.file, content); const r = await f.call(['list', '--json'], on); assert.equal(r.code, 0); assert.equal('clientUpdate' in r.value, false); await f.settled(count + 1); assert.equal(f.requests.length, count + 1); - assert.equal(JSON.parse(readFileSync(f.file, 'utf8')).latest, '0.5.1'); + assert.equal(JSON.parse(readFileSync(f.file, 'utf8')).latest, '0.5.2'); } }); test('concurrent invocations perform one bounded refresh; locks and failures back off', async t => { - const f = await fixture(t, { body: JSON.stringify({ latest: '0.5.1' }), wait: 1000 }); + const f = await fixture(t, { body: JSON.stringify({ latest: '0.5.2' }), wait: 1000 }); const on = { SESSION_PEER_UPDATE_NOTICE: '1' }; const results = await Promise.all(Array.from({ length: 8 }, () => f.call(['list', '--json'], on))); for (const r of results) { assert.equal(r.code, 0); assert.equal('clientUpdate' in r.value, false); } @@ -341,7 +341,7 @@ test('concurrent invocations perform one bounded refresh; locks and failures bac await f.settled(2); assert.equal(f.requests.length, 2); // Offline/failed refresh records a backoff without a version and never alters output. f.state.reply = { status: 503, body: SENTINEL }; - f.seed('0.5.1', 25 * 3600_000); + f.seed('0.5.2', 25 * 3600_000); const offline = await f.call(['list', '--json'], on); assert.equal(offline.code, 0); assert.equal(offline.stdout.includes(SENTINEL), false); await f.settled(3); assert.equal(f.requests.length, 3); @@ -360,7 +360,7 @@ test('remote wire requests never read, refresh or report client notices', async const on = { SESSION_PEER_UPDATE_NOTICE: '1' }; const wire = await f.call(['--stdio-request'], on, JSON.stringify({ schemaVersion: 1, args: ['list', '--json'] })); assert.equal(wire.code, 0); assert.equal('clientUpdate' in wire.value, false); - f.seed('0.5.1'); + f.seed('0.5.2'); const seeded = await f.call(['--stdio-request'], on, JSON.stringify({ schemaVersion: 1, args: ['doctor', '--json'] })); assert.equal('clientUpdate' in seeded.value, false); await delay(1000); @@ -371,7 +371,7 @@ test('remote wire requests never read, refresh or report client notices', async test('over SSH the local client adds its own notice; the receiver never reads or refreshes', { skip: process.platform === 'win32' && 'POSIX fake ssh' }, async t => { const f = await fixture(t); - f.seed('0.5.1'); + f.seed('0.5.2'); const bin = join(f.root, 'bin'), receiver = join(f.root, 'receiver-cache'); mkdirSync(bin); writeFileSync(join(bin, 'ssh'), `#!${process.execPath}\nconst {spawnSync}=require('node:child_process');const flag=process.argv.at(-1).endsWith('--version')?'--version':'--stdio-request';` + @@ -380,7 +380,7 @@ test('over SSH the local client adds its own notice; the receiver never reads or const on = { SESSION_PEER_UPDATE_NOTICE: '1', PATH: `${bin}${delimiter}${process.env.PATH}` }; const json = await f.call(['list', '--host', 'audit', '--json'], on); assert.equal(json.code, 0, json.stdout); assert.equal(json.value.host, 'audit'); - assert.equal(json.value.clientUpdate.latest, '0.5.1'); assert.equal(json.stderr, ''); + assert.equal(json.value.clientUpdate.latest, '0.5.2'); assert.equal(json.stderr, ''); const text = await f.call(['list', '--host', 'audit', '--output-format', 'text'], on); assert.equal(text.code, 0); assert.equal(text.stderr.match(/Update available/g)?.length, 1); const off = await f.call(['list', '--host', 'audit', '--json', '--no-update-notice'], on); @@ -430,9 +430,9 @@ test('explicit and background cache writes are serialized by the same lock and n const locked = await f.call(['update', '--check', '--json']); assert.equal(locked.code, 0); assert.equal(locked.value.latest, '0.3.9'); assert.equal(locked.value.cache, 'skipped_locked'); assert.equal(existsSync(f.file), false); - f.state.reply = { body: JSON.stringify({ latest: '0.5.1' }) }; + f.state.reply = { body: JSON.stringify({ latest: '0.5.2' }) }; await refreshCache({ ...f.env, SESSION_PEER_UPDATE_LOCK_TOKEN: background }); - assert.equal(JSON.parse(readFileSync(f.file, 'utf8')).latest, '0.5.1'); assert.equal(existsSync(f.lock), false); + assert.equal(JSON.parse(readFileSync(f.file, 'utf8')).latest, '0.5.2'); assert.equal(existsSync(f.lock), false); f.state.reply = { body: JSON.stringify({ latest: '0.3.9' }) }; const written = await f.call(['update', '--check', '--json']); assert.equal(written.value.cache, 'written'); assert.equal(JSON.parse(readFileSync(f.file, 'utf8')).latest, '0.3.9'); @@ -440,7 +440,7 @@ test('explicit and background cache writes are serialized by the same lock and n writeFileSync(f.file, JSON.stringify({ schemaVersion: 1, package: 'session-peer', source: 'npm_registry', channel: 'latest', latest: '0.3.9', checkedAt: Date.now() + 60_000 })); const newer = readFileSync(f.file, 'utf8'); const late = acquireLock(env)!; - f.state.reply = { body: JSON.stringify({ latest: '0.5.1' }) }; + f.state.reply = { body: JSON.stringify({ latest: '0.5.2' }) }; await refreshCache({ ...f.env, SESSION_PEER_UPDATE_LOCK_TOKEN: late }); assert.equal(readFileSync(f.file, 'utf8'), newer); assert.equal(existsSync(f.lock), false); const skipped = await f.call(['update', '--check', '--json']); @@ -465,7 +465,7 @@ test('concurrent lock acquisition across processes yields one owner, and none fo }); test('lock I/O failures: a failed token write removes only its own new lock; an unverifiable lock maps to cache failed', async t => { - const f = await fixture(t, { body: JSON.stringify({ latest: '0.5.1' }) }); + const f = await fixture(t, { body: JSON.stringify({ latest: '0.5.2' }) }); const env = { ...f.env }; t.after(() => { t.mock.restoreAll(); syncBuiltinESMExports(); }); const token = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/; @@ -487,7 +487,7 @@ test('lock I/O failures: a failed token write removes only its own new lock; an syncBuiltinESMExports(); const result = await checkUpdate('latest', env); t.mock.restoreAll(); syncBuiltinESMExports(); - assert.equal(result.cache, 'failed'); assert.equal(result.latest, '0.5.1'); assert.equal(existsSync(f.file), false); + assert.equal(result.cache, 'failed'); assert.equal(result.latest, '0.5.2'); assert.equal(existsSync(f.file), false); // Release failed too, so the lock is left for documented manual recovery. assert.ok(existsSync(f.lock)); }); From 4004290462195acbc3cd7cf0de133f0e60190f59 Mon Sep 17 00:00:00 2001 From: YEONGUK LEE Date: Tue, 27 Oct 2026 09:51:16 +0900 Subject: [PATCH 2/2] docs(release): record current 0.5.1 settings checks and exception --- RELEASING.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/RELEASING.md b/RELEASING.md index 403da4d..a84633d 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -23,6 +23,15 @@ The read-only registry baseline is latest=0.5.0, preview=0.1.0-preview.1 and Previous provenance endpoint: `https://registry.npmjs.org/-/npm/v1/attestations/session-peer@0.5.0`. +For this 0.5.1 release, the user explicitly skipped the browser-only package +publish-MFA policy read. That setting is not freshly audited. Authenticated npm +ownership and absence of pending stages were confirmed; the fresh Trusted +Publisher lookup returned repository `abruption/session-peer-ts`, workflow +`publish.yml`, environment `npm` and only `createStagedPackage` permission. +The protected GitHub environment and actual final npm stage 2FA approval remain +required. No publisher/protection/credential settings were changed. This +settings-read exception applies only to 0.5.1. + 1. Merge the reviewed patch under normal protections and require the exact merged-main release gate, Windows Node 22/24 and CodeQL checks. 2. Freshly verify npm ownership, pending stages, stage-only Trusted Publisher,