From 37c11fab23a6b137748e62937adcb3b1b6122615 Mon Sep 17 00:00:00 2001 From: ShutenOishi Date: Mon, 5 Oct 2026 08:05:33 +0900 Subject: [PATCH 1/4] Prepare unpublished rc1 scope and fail-closed Process package audit --- AGENTS.md | 1 + README.ja.md | 6 +- README.md | 6 +- ...preview-engineering-without-publication.md | 3 +- ...025-freeze-features-for-unpublished-rc1.md | 56 +++++++ docs/adr/README.md | 4 +- docs/architecture.md | 11 ++ docs/phase-8-rc1.md | 46 ++++++ docs/phase-8-stabilization.md | 7 + docs/releases/nuget-1.0.0-rc.1.md | 38 +++++ docs/roadmap.md | 14 ++ eng/audit_candidate.py | 18 +- eng/rc1.packages.lock.json | 155 ++++++++++++++++++ eng/test_candidate_audit.py | 14 +- eng/test_candidate_preflight.py | 30 ++++ eng/validate_candidate.py | 5 + eng/verify_candidate_ci.py | 6 + 17 files changed, 408 insertions(+), 12 deletions(-) create mode 100644 docs/adr/0025-freeze-features-for-unpublished-rc1.md create mode 100644 docs/phase-8-rc1.md create mode 100644 docs/releases/nuget-1.0.0-rc.1.md create mode 100644 eng/rc1.packages.lock.json diff --git a/AGENTS.md b/AGENTS.md index 4ead812..4bd3a47 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -28,6 +28,7 @@ If an Accepted decision changes, create a new superseding ADR instead of rewriti - Validate option availability and incompatible combinations before starting the process. - Use `ProcessStartInfo.ArgumentList` on modern targets. The `netstandard2.0` compatibility backend shares the Process lifecycle with attributed native argument serialization, race-safe exit events and best-effort legacy Windows tree termination according to ADR-0024. Do not execute PostgreSQL tools through `cmd.exe`, PowerShell, `bash -c`, or another shell. - Keep the core package free of unnecessary runtime dependencies. +- Under ADR-0025, until 1.0 accept only bug fixes, validation and documentation improvements; do not add new features. RC engineering, 1.0 acceptance and external publication are separate gates. - Public APIs must have bilingual English/Japanese XML documentation according to `docs/localization.md`. - Human-facing project surfaces must provide Japanese as well as English. This includes the repository README, GitHub Release titles/notes, user-oriented guides, and other prominent public documentation. The root README may link prominently to a maintained `README.ja.md` instead of duplicating the full text inline. - User-facing diagnostics and exception messages must be localizable in English and Japanese using resources; do not hard-code localized strings throughout the implementation. diff --git a/README.ja.md b/README.ja.md index 048630f..5b3bdaf 100644 --- a/README.ja.md +++ b/README.ja.md @@ -7,7 +7,7 @@ PostgreSQL のコマンドラインツールを、型安全な .NET API から ## 現在の状況 -全25ラッパーとレビュー済みAPIを実装し、Phase 8で未公開 `0.1.0-alpha.2` プレビューを準備しています。ラムダ設定と実行しないコマンド生成も含みます。[最終レビュー](docs/phase-8-final-review.md)で検証範囲と公開前条件を記録します。1.0の安定性を保証するものではありません。旧alpha.1はADR-0012により履歴として保存します。 +全25ラッパー、レビュー済みAPI、ラムダ設定と実行しないコマンド生成を実装済みです。Phase 8では共有Process実装を含む未公開 `1.0.0-rc.1` を準備します。ADR-0025により1.0までは不具合修正・検証・文書改善に限定します。[RC準備](docs/phase-8-rc1.md)に固定・監査の条件と未検証範囲を記録します。1.0昇格や公開承認ではなく、旧alpha.1/alpha.2は履歴として保存します。 初期対応範囲: @@ -20,7 +20,7 @@ Phase 0〜2 の既存 GitHub Release は維持します。ADR-0012 により Pha ## NuGet プレビュー -選択したプレビュー版は `0.1.0-alpha.2` で、nuget.orgには未公開です。CIの未公開候補artifactを取得して `./candidate-packages` へ展開した場合は、利用側で次のように指定できます。 +nuget.orgには未公開です。`1.0.0-rc.1` は準備中で、正確なソースの固定と3OS候補監査が成功してから使用してください。manifestと監査済みartifactで出所を確認します。保存したalpha.2は旧実装で、RCではありません。検証済みRC artifactを `./candidate-packages` へ展開後、利用側で次のように指定できます。 利用側プロジェクトの横に次の `NuGet.Config` を作り、候補と別途解決する依存をWindows/Linux/macOSで復元できるようにします: @@ -35,7 +35,7 @@ Phase 0〜2 の既存 GitHub Release は維持します。ADR-0012 により Pha ``` ```bash -dotnet add package PgCliSharp --version 0.1.0-alpha.2 +dotnet add package PgCliSharp --version 1.0.0-rc.1 dotnet restore ``` diff --git a/README.md b/README.md index f637f01..d02bf3a 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Strongly typed .NET wrapper for PostgreSQL command-line tools. ## Project status -All 25 wrappers and the reviewed API are implemented. Phase 8 prepares the unpublished `0.1.0-alpha.2` preview, including lambda configuration and offline command generation. See the [final review](docs/phase-8-final-review.md) for tested scope and remaining publication gates. This is not a 1.0 stability claim. Historical alpha.1 remains preserved under ADR-0012. +All 25 wrappers and the reviewed API are implemented, including lambda configuration and offline command generation. Phase 8 now prepares unpublished `1.0.0-rc.1` with the shared Process backend. Until 1.0, changes are limited to bug fixes, validation and documentation under ADR-0025. See [RC preparation](docs/phase-8-rc1.md) for selection/audit gates and exclusions. This is not 1.0 acceptance or publication approval; historical alpha.1/alpha.2 remain preserved. Initial PostgreSQL compatibility target: @@ -20,7 +20,7 @@ Phase 0-2 retain their existing GitHub Releases. Under ADR-0012, Phase 3 onward ## NuGet preview -The selected preview version is `0.1.0-alpha.2`; it is not published to nuget.org. After downloading an unpublished candidate CI artifact and extracting it to `./candidate-packages`, a local consumer can use: +No package is published to nuget.org. `1.0.0-rc.1` is being prepared; use it only after its exact source is selected and all three-OS candidate audits pass. The manifest and audited artifact identify that source. Preserved alpha.2 artifacts contain the old backend and are not RCs. After extracting the verified RC artifact to `./candidate-packages`, a local consumer can use: Create a project-local `NuGet.Config` beside the consumer project so both the candidate and its separately resolved dependencies can restore on Windows/Linux/macOS: @@ -35,7 +35,7 @@ Create a project-local `NuGet.Config` beside the consumer project so both the ca ``` ```bash -dotnet add package PgCliSharp --version 0.1.0-alpha.2 +dotnet add package PgCliSharp --version 1.0.0-rc.1 dotnet restore ``` diff --git a/docs/adr/0021-finish-preview-engineering-without-publication.md b/docs/adr/0021-finish-preview-engineering-without-publication.md index ded0fe6..82c8f65 100644 --- a/docs/adr/0021-finish-preview-engineering-without-publication.md +++ b/docs/adr/0021-finish-preview-engineering-without-publication.md @@ -1,6 +1,7 @@ # ADR-0021: Finish preview engineering without publication -- Status: Accepted +- Status: Superseded +- Superseded by: ADR-0025 - Date: 2026-10-03 - Supersedes: ADR-0020 - Complements: ADR-0012, ADR-0016, ADR-0019 diff --git a/docs/adr/0025-freeze-features-for-unpublished-rc1.md b/docs/adr/0025-freeze-features-for-unpublished-rc1.md new file mode 100644 index 0000000..6a3c6a2 --- /dev/null +++ b/docs/adr/0025-freeze-features-for-unpublished-rc1.md @@ -0,0 +1,56 @@ +# ADR-0025: Freeze features for an unpublished 1.0 release candidate + +- Status: Accepted +- Date: 2026-10-05 +- Supersedes: ADR-0021 +- Complements: ADR-0012, ADR-0017, ADR-0018, ADR-0022, ADR-0024 + +## Context + +PR #26 replaced CliWrap with a shared Process lifecycle. Exact main +`d66a1e229d230d5f6d7b6fdd8db0c79a85bba89c` passed CI 37194612585: +18 jobs, attempt 1, independently checked Windows 30/3/3 stress, native +Windows/macOS PostgreSQL 18 and Linux 10–18 evidence. The fixed unpublished +alpha.2 predates this replacement. It is not a distribution of current main. +The maintainer chooses bug fixes, validation and documentation only until 1.0. + +## Decision + +- Prepare unpublished `1.0.0-rc.1`; stop feature additions until the scoped 1.0 + acceptance decision. Fixes must preserve the frozen API where possible; + any necessary contract change still requires an explicit ADR and review. +- Keep PostgreSQL 10–18, all three TFMs, explicit executable paths, MIT, + typed/lambda/offline APIs, bilingual documentation and existing exclusions. +- First test RC source preparation on the exact final PR and main. Only then + select that successful main SHA/CI in a second reviewed control change. + This avoids self-referential source selection and shipping stale preview guidance. +- Keep SDK 10.0.100, a separate Process-backend dependency lock and known + audit profile. Require all 18 source CI jobs at attempt 1, including both native + jobs, nine Linux majors, three deterministic suites, three candidate audits + and the shared read-only preflight. Reject missing, duplicate or skipped jobs. +- RC selection must pass three-OS locked package/symbol/SourceLink/consumer + audits on its exact PR/main controls. Only version and release-note metadata + are overridden in the selected source. No third-party or PostgreSQL binaries + are embedded. Runtime dependencies must match the two reviewed legacy packages. +- Preserve alpha.2 source, lock, notes and selection history, and disabled Phase 3 + manifests. Do not reuse or silently rewrite an old candidate's provenance. +- Publication remains disabled. RC engineering completion is neither 1.0 + promotion nor approval for a tag, GitHub Release, assets or NuGet push. + Account-side Trusted Publishing remains unverified. + +## Acceptance and limits + +RC preparation, RC package selection/audit, 1.0 acceptance and external publication +are separate gates. Existing real-CLI exclusions remain explicit. Historical +stalls and the cancelled replacement run are retained, not erased by green runs; +no complete historical root-cause proof or universal concurrency guarantee is claimed. +No new production implementation or public API change is introduced here. + +## 日本語 + +1.0までは新機能追加を止め、不具合修正・検証・文書改善に絞ります。 +未公開`1.0.0-rc.1`を準備し、まず説明・監査準備を含む正確なmainを検証してから、 +その成功済みSHA/CIを候補へ固定します。SDK・別lock・既知依存profile・全18ジョブを +必須にし、3OSで実パッケージ・PDB・利用側を監査します。旧alpha.2のソース・lock・ +説明・履歴は保存します。API・PG10〜18・3TFM・MIT・未検証範囲は保持します。 +RC準備と1.0昇格は別で、公開設定は無効のまま、タグ・Release・NuGet公開は行いません。 diff --git a/docs/adr/README.md b/docs/adr/README.md index 5fc7305..7dd4e73 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -78,11 +78,11 @@ Do not silently edit an old Accepted ADR to make history appear different. | [0018](0018-offline-commands-and-configuration-snapshots.md) | Add lambda configuration, offline commands and execution snapshots | Accepted | | [0019](0019-adopt-mit-license.md) | Adopt the MIT license | Accepted | | [0020](0020-select-unpublished-full-wrapper-preview.md) | Select an unpublished full-wrapper preview | Superseded | -| [0021](0021-finish-preview-engineering-without-publication.md) | Finish preview engineering without publication | Accepted | +| [0021](0021-finish-preview-engineering-without-publication.md) | Finish preview engineering without publication | Superseded | | [0022](0022-require-three-os-native-cli-checkpoint.md) | Require a three-OS native CLI checkpoint for 1.0 | Accepted | | [0023](0023-isolate-legacy-startup-synchronization-context.md) | Isolate the legacy backend's startup synchronization context | Superseded | - | [0024](0024-share-process-backend-with-legacy-compatibility.md) | Share the Process backend with narrow legacy compatibility | Accepted | +| [0025](0025-freeze-features-for-unpublished-rc1.md) | Freeze features for an unpublished 1.0 release candidate | Accepted | ## Template diff --git a/docs/architecture.md b/docs/architecture.md index c720f76..3e152ff 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -308,6 +308,17 @@ The core package should avoid unnecessary dependencies such as Npgsql or Microso ## 15. NuGet and release policy +ADR-0025 freezes new features until 1.0: only bug fixes, verification and +documentation improvements proceed. Unpublished `1.0.0-rc.1` preparation uses a +separate Process-backend lock/profile and a successful exact-main source selected +after source preparation CI. All 18 source jobs and three-OS candidate audits are +mandatory. Alpha.2 remains historical evidence, not current-backend distribution. +RC completion does not promote 1.0 or enable publication. + +日本語: 1.0までは不具合修正・検証・文書改善に限定します。未公開rc.1は準備mainの +全18CI成功後に固定し、別lockと3OS候補監査を使います。旧alpha.2を保存し、 +RC完了・1.0昇格・外部公開を区別します。 + ADR-0019 licenses original PgCliSharp code/documentation under MIT. Packages declare the MIT expression and include the root LICENSE and bilingual licensing guide. PostgreSQL executables are externally supplied, not bundled or relicensed. diff --git a/docs/phase-8-rc1.md b/docs/phase-8-rc1.md new file mode 100644 index 0000000..a11dfb2 --- /dev/null +++ b/docs/phase-8-rc1.md @@ -0,0 +1,46 @@ +# Unpublished 1.0.0-rc.1 preparation / 未公開RC準備 + +## English + +Status: source preparation; candidate selection and three-OS audit pending. +ADR-0025 limits remaining 1.0 work to bug fixes, validation and documentation. +All 25 wrappers, lambda configuration, offline commands and the reviewed API +remain unchanged. PR #26 / main `d66a1e229d230d5f6d7b6fdd8db0c79a85bba89c` +completed the Process-backend replacement through all 18 CI jobs at attempt 1; +[the exact artifact receipt](https://github.com/ShutenOishi/pgcli-sharp/pull/26#issuecomment-5978994921) +records Windows 30/3/3 stress and actual three-OS PostgreSQL evidence. + +1. Prepare current source documentation, RC notes and fail-closed audit controls. +2. Require fresh final PR and exact-main all-18 CI and actual artifact inspection. +3. Fix that successful source SHA/main CI as `1.0.0-rc.1`, retaining alpha.2 + source/lock/notes/history. Use SDK 10.0.100 and the new reviewed dependency lock. +4. Audit the actual nupkg/snupkg, all three PDB/DLL pairs, byte-exact source docs, + exact MIT runtime dependencies and isolated consumers on Windows/Linux/macOS. + Windows runs net48; netstandard itself is compile-only. Negative tests must fail. +5. Require exact final selection PR/main CI; retain links, hashes and exclusions. + +No RC artifact exists merely because these instructions or notes exist. The +machine-readable `.github/release-candidate.json` and its verified CI artifacts +identify the currently fixed candidate. Historical alpha.2 includes CliWrap and +does not contain the new backend. Ordinary development artifacts are not RCs. + +Real evidence is Linux PostgreSQL 10–18/net10 and native Windows/macOS 18/net10. +Other native majors/TFMs, service lifecycle, TLS/ICU/TTY, optional compression, +other upgrade pairs and unlisted scenarios remain excluded. A finite successful +stress run does not prove every possible scheduling behavior or the past root cause. + +Publication remains disabled: no tag, Release, assets or NuGet push. Successful +RC engineering is not 1.0 promotion. Trusted Publishing account verification and +publication approval are still separate. RC feedback/fixes and final scoped +acceptance determine a future 1.0 selection. + +## 日本語 + +現在はソース準備段階で、候補の固定と3OS監査は未完です。新機能を止め、 +不具合修正・検証・文書改善に限定します。Process置換はPR #26と正確なmainで +全18CI・Windows30/3/3反復・3OS実PostgreSQL成果物まで確認済みです。 +準備PR/mainを検証後、その成功済みソースをrc.1へ固定し、旧alpha.2の履歴を保持します。 +別lock・固定SDKで実nupkg/snupkg・PDB/DLL・原文・依存・利用側と異常系を監査します。 +説明ファイルがあるだけでは候補完成とせず、manifestと実CI成果物で判断します。 +旧alpha.2は旧CliWrap実装で、通常開発artifactもRCとは別です。 +未検証範囲と過去の失敗は残し、1.0昇格・公開承認・Trusted Publishing確認は別途必要です。 diff --git a/docs/phase-8-stabilization.md b/docs/phase-8-stabilization.md index f994b5b..04083c2 100644 --- a/docs/phase-8-stabilization.md +++ b/docs/phase-8-stabilization.md @@ -5,6 +5,13 @@ Status: **In progress, not release-ready.** Baseline is the completed Phase 7 main commit `c857161047ec0b7027b6061fa8f19c688e60dc94`. +ADR-0025 starts [unpublished rc.1 preparation](phase-8-rc1.md) with new features +frozen. PR #26's replacement is complete through exact main CI 37194612585 and +independent artifacts; historical failures remain in its receipt. Candidate +selection/audit, 1.0 acceptance and publication are still separate pending gates. +日本語: 新機能を止めrc.1を準備します。Process置換は完了、候補の固定・監査、 +1.0昇格と公開は別途残します。 + Stabilization is split into bounded checkpoints. Completing one checkpoint does not imply API freeze, full real-binary compatibility or publication approval. diff --git a/docs/releases/nuget-1.0.0-rc.1.md b/docs/releases/nuget-1.0.0-rc.1.md new file mode 100644 index 0000000..037dbc9 --- /dev/null +++ b/docs/releases/nuget-1.0.0-rc.1.md @@ -0,0 +1,38 @@ +# PgCliSharp 1.0.0-rc.1 — Unpublished release candidate / 未公開リリース候補 + +## English + +Planned unpublished release candidate; exact selection and package-audit evidence +are recorded by `.github/release-candidate.json` and the RC validation receipt. +Do not infer availability on nuget.org or GitHub Releases from this file. + +- All 25 typed PostgreSQL CLI wrappers; PostgreSQL 10–18, .NET Standard 2.0/.NET 8/.NET 10. +- Reviewed frozen API, configuration lambdas, execution snapshots and offline command generation. +- Shared Process execution lifecycle without CliWrap; narrow legacy argument/exit compatibility, + bounded input, original cancellation/deadlines and best-effort Windows descendant termination. +- MIT original code, bilingual docs/messages, shipped upstream notices; PostgreSQL binaries are not bundled. +- New features stop until 1.0; only bug fixes, validation and documentation improvements proceed. + +Required audit: exact successful source-main CI; fixed SDK/lock; actual package +payload/license/dependency checks; three portable PDB/DLL pairs and negative +SourceLink tests; isolated compile/runtime consumers on all three OS controls. +Real-CLI evidence covers pinned Linux10–18 and native Windows/macOS18/net10. +Existing native-major/TFM, service, TLS/ICU/TTY, optional compression and migration +exclusions remain. Historical stalls are not relabeled as solved root causes. + +This is not `1.0.0`, a universal compatibility guarantee or publication approval. +No public artifacts or tags are created; Trusted Publishing account verification +remains pending. Alpha.1/alpha.2 provenance remains history, never silently replaced. + +## 日本語 + +未公開リリース候補の準備です。実際の固定ソースと監査結果はmanifestと検証記録で +確認し、このファイルだけでnuget.org/GitHub Releaseへの公開とは判断しません。 +全25型付きCLI、PG10〜18、3TFM、ラムダ設定・実行snapshot・オフラインコマンド、 +CliWrapを使わない共有Process実装を含みます。入力上限・EOF・キャンセル・期限・ +日英対応・MITと第三者表示を保持し、PostgreSQL本体は同梱しません。 +1.0までは不具合修正・検証・文書改善に限定し、新機能を追加しません。 +固定SDK/lockと成功済みmainを使い、実パッケージ・PDB・依存・利用側を3OSで監査します。 +Linux10〜18とnative Windows/macOS18の実CLI証拠、既存の未検証範囲を分けて保持します。 +RCは1.0・全組合せ保証・公開承認ではありません。外部公開とTrusted Publishing確認は +延期し、旧候補のソースと履歴を保存します。 diff --git a/docs/roadmap.md b/docs/roadmap.md index b885262..da5d918 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -134,6 +134,20 @@ Tools that can alter or recover data directories require especially explicit doc ## Phase 8 - 1.0 stabilization +ADR-0025 now limits 1.0 changes to bug fixes, validation and documentation. +Unpublished `1.0.0-rc.1` [source preparation and selection gates](phase-8-rc1.md) +are in progress. PR #26's Process replacement is complete at main +`d66a1e229d230d5f6d7b6fdd8db0c79a85bba89c`, exact CI 37194612585, all 18 jobs +at attempt 1, with independently inspected actual artifacts. See the +[final receipt](https://github.com/ShutenOishi/pgcli-sharp/pull/26#issuecomment-5978994921). +The fixed alpha.2 is retained until a successful RC preparation main is selected; +it does not contain the replacement. RC engineering, 1.0 acceptance and publication +are separate; publication remains disabled. + +日本語: 1.0までは不具合修正・検証・文書改善に絞ります。PR #26の置換は正確なmainの +全18CI・実成果物確認で完了です。rc.1準備mainを成功後に固定し、旧alpha.2を保存します。 +RC準備・1.0判定・公開は別で、公開は無効のままです。以下は過去の調査・選定の記録です。 + CP-05 has completed through PR #23, exact main `f5f7ecbb4f5d04647bc3b2eb570d4e1aa2c503b3` and CI 37115096538 (18 jobs, attempt 1), with independent artifact inspection. The remaining [Windows net48 completion investigation](phase-8-net48-investigation.md) diff --git a/eng/audit_candidate.py b/eng/audit_candidate.py index f361cc8..892a218 100644 --- a/eng/audit_candidate.py +++ b/eng/audit_candidate.py @@ -14,6 +14,18 @@ ROOT = Path(__file__).resolve().parent.parent FRAMEWORKS = ("netstandard2.0", "net8.0", "net10.0") +PROFILES = { + "legacy-cliwrap": ("eng/candidate.packages.lock.json", [("CliWrap", "3.10.5")]), + "process-compat-v1": ("eng/rc1.packages.lock.json", [("System.Management", "10.0.10")]), +} + + +def profile(candidate): + name = candidate.get("dependency_profile", "legacy-cliwrap") + assert name in PROFILES, "Unknown candidate dependency profile" + return PROFILES[name] + + LEGACY_LICENSES = { "NETStandard.Library/2.0.3": ("LICENSE.TXT", "fc95de1436a321aadbfafd21f8a8506a2f4678bfc003bd806d6b59d3088efadc", "MIT"), "Microsoft.NETCore.Platforms/1.1.0": ("dotnet_library_license.txt", "f1db688d8481c91a452fabcea5060a23da9ea5088329b58c478a040e2e426297", "LicenseRef-Microsoft-DotNet-Library"), @@ -67,7 +79,7 @@ def audit_package(package, source, candidate): assert {group.get("targetFramework") for group in groups} == {".NETStandard2.0", "net8.0", "net10.0"} for group in groups: deps = [(node.get("id"), node.get("version")) for node in group.findall("dependency")] - assert deps == ([("CliWrap", "3.10.5")] if group.get("targetFramework") == ".NETStandard2.0" else []) + assert deps == (profile(candidate)[1] if group.get("targetFramework") == ".NETStandard2.0" else []) return {"file": package.name, "sha256": sha256(package.read_bytes()), "payload": archive.namelist()} @@ -234,7 +246,9 @@ def main(): assert sdk == candidate["sdk_version"], "Wrong candidate SDK" project = source / "src/PgCliSharp/PgCliSharp.csproj" common = ("-m:1", "-nodeReuse:false", "-p:UseSharedCompilation=false") - pinned_lock = ROOT / "eng/candidate.packages.lock.json" + pinned_lock = ROOT / profile(candidate)[0] + if "lock_sha256" in candidate: + assert sha256(pinned_lock.read_bytes()) == candidate["lock_sha256"], "Wrong candidate lock" (project.parent / "packages.lock.json").write_bytes(pinned_lock.read_bytes()) run(args.dotnet, "restore", str(project), "--locked-mode", *common, cwd=output) run(args.dotnet, "pack", str(project), "-c", "Release", "--no-restore", "-o", str(output), diff --git a/eng/rc1.packages.lock.json b/eng/rc1.packages.lock.json new file mode 100644 index 0000000..9e9f9ae --- /dev/null +++ b/eng/rc1.packages.lock.json @@ -0,0 +1,155 @@ +{ + "version": 1, + "dependencies": { + ".NETStandard,Version=v2.0": { + "Microsoft.SourceLink.GitHub": { + "type": "Direct", + "requested": "[10.0.401, )", + "resolved": "10.0.401", + "contentHash": "LGmlwgP1Cx37JEWzyjS0o1/+xs/s/e3E2TBSuogI5ePA/9L0pfIfeYX0k5in7Bfcw8Nn2y2sG9jXxydTjiR2Fg==", + "dependencies": { + "Microsoft.Build.Tasks.Git": "10.0.401", + "Microsoft.SourceLink.Common": "10.0.401", + "System.IO.Hashing": "10.0.12" + } + }, + "NETStandard.Library": { + "type": "Direct", + "requested": "[2.0.3, )", + "resolved": "2.0.3", + "contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0" + } + }, + "System.Management": { + "type": "Direct", + "requested": "[10.0.10, )", + "resolved": "10.0.10", + "contentHash": "U13ksdXfQlF2WDdu3S4ZPVpikFs2HgV3dbJJZLeZpBIQ4z/xCmQnwEf6LsEEIC2zSamVVa1acA9RdVhZwnWKeA==", + "dependencies": { + "System.CodeDom": "10.0.10" + } + }, + "Microsoft.Build.Tasks.Git": { + "type": "Transitive", + "resolved": "10.0.401", + "contentHash": "ZYctNuT10V9IYyCFydy63DXx0ggZQuynuzQOdLvW62dPgzjIz7f0ISEP75RGiq1jFQh8p6TmGSqxeQZQ87LCig==", + "dependencies": { + "System.IO.Hashing": "10.0.12" + } + }, + "Microsoft.NETCore.Platforms": { + "type": "Transitive", + "resolved": "1.1.0", + "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" + }, + "Microsoft.SourceLink.Common": { + "type": "Transitive", + "resolved": "10.0.401", + "contentHash": "u3rLxIwi/9MqDFaWGE/QQgLR1NBEzLOW2lv5+9OrZPDBYIAmFdYSWCWrR1ufpXWOqFn+x02TgKropl/oDuHmgA==" + }, + "System.Buffers": { + "type": "Transitive", + "resolved": "4.6.1", + "contentHash": "N8GXpmiLMtljq7gwvyS+1QvKT/W2J8sNAvx+HVg4NGmsG/H+2k/y9QI23auLJRterrzCiDH+IWAw4V/GPwsMlw==" + }, + "System.CodeDom": { + "type": "Transitive", + "resolved": "10.0.10", + "contentHash": "lru5igKxs4OgWKfMeOpMAYpfB1tNSSXHk2zyiSDO/h0u8RVAhN6KqW9d3ANybDB+ePIHg6HM/NlE8HlpOk4k5A==" + }, + "System.IO.Hashing": { + "type": "Transitive", + "resolved": "10.0.12", + "contentHash": "jDix4bBMYnpZdSPcnY+KDV6ik3SRMzpMKby/bZl/XUwIiflwRNAFZ0oOl61R/pSaveIJ8t1gs2BUlrGsPs/bcg==", + "dependencies": { + "System.Buffers": "4.6.1", + "System.Memory": "4.6.3" + } + }, + "System.Memory": { + "type": "Transitive", + "resolved": "4.6.3", + "contentHash": "qdcDOgnFZY40+Q9876JUHnlHu7bosOHX8XISRoH94fwk6hgaeQGSgfZd8srWRZNt5bV9ZW2TljcegDNxsf+96A==", + "dependencies": { + "System.Buffers": "4.6.1", + "System.Numerics.Vectors": "4.6.1", + "System.Runtime.CompilerServices.Unsafe": "6.1.2" + } + }, + "System.Numerics.Vectors": { + "type": "Transitive", + "resolved": "4.6.1", + "contentHash": "sQxefTnhagrhoq2ReR0D/6K0zJcr9Hrd6kikeXsA1I8kOCboTavcUC4r7TSfpKFeE163uMuxZcyfO1mGO3EN8Q==" + }, + "System.Runtime.CompilerServices.Unsafe": { + "type": "Transitive", + "resolved": "6.1.2", + "contentHash": "2hBr6zdbIBTDE3EhK7NSVNdX58uTK6iHW/P/Axmm9sl1xoGSLqDvMtpecn226TNwHByFokYwJmt/aQQNlO5CRw==" + } + }, + "net10.0": { + "Microsoft.SourceLink.GitHub": { + "type": "Direct", + "requested": "[10.0.401, )", + "resolved": "10.0.401", + "contentHash": "LGmlwgP1Cx37JEWzyjS0o1/+xs/s/e3E2TBSuogI5ePA/9L0pfIfeYX0k5in7Bfcw8Nn2y2sG9jXxydTjiR2Fg==", + "dependencies": { + "Microsoft.Build.Tasks.Git": "10.0.401", + "Microsoft.SourceLink.Common": "10.0.401", + "System.IO.Hashing": "10.0.12" + } + }, + "Microsoft.Build.Tasks.Git": { + "type": "Transitive", + "resolved": "10.0.401", + "contentHash": "ZYctNuT10V9IYyCFydy63DXx0ggZQuynuzQOdLvW62dPgzjIz7f0ISEP75RGiq1jFQh8p6TmGSqxeQZQ87LCig==", + "dependencies": { + "System.IO.Hashing": "10.0.12" + } + }, + "Microsoft.SourceLink.Common": { + "type": "Transitive", + "resolved": "10.0.401", + "contentHash": "u3rLxIwi/9MqDFaWGE/QQgLR1NBEzLOW2lv5+9OrZPDBYIAmFdYSWCWrR1ufpXWOqFn+x02TgKropl/oDuHmgA==" + }, + "System.IO.Hashing": { + "type": "Transitive", + "resolved": "10.0.12", + "contentHash": "jDix4bBMYnpZdSPcnY+KDV6ik3SRMzpMKby/bZl/XUwIiflwRNAFZ0oOl61R/pSaveIJ8t1gs2BUlrGsPs/bcg==" + } + }, + "net8.0": { + "Microsoft.SourceLink.GitHub": { + "type": "Direct", + "requested": "[10.0.401, )", + "resolved": "10.0.401", + "contentHash": "LGmlwgP1Cx37JEWzyjS0o1/+xs/s/e3E2TBSuogI5ePA/9L0pfIfeYX0k5in7Bfcw8Nn2y2sG9jXxydTjiR2Fg==", + "dependencies": { + "Microsoft.Build.Tasks.Git": "10.0.401", + "Microsoft.SourceLink.Common": "10.0.401", + "System.IO.Hashing": "10.0.12" + } + }, + "Microsoft.Build.Tasks.Git": { + "type": "Transitive", + "resolved": "10.0.401", + "contentHash": "ZYctNuT10V9IYyCFydy63DXx0ggZQuynuzQOdLvW62dPgzjIz7f0ISEP75RGiq1jFQh8p6TmGSqxeQZQ87LCig==", + "dependencies": { + "System.IO.Hashing": "10.0.12" + } + }, + "Microsoft.SourceLink.Common": { + "type": "Transitive", + "resolved": "10.0.401", + "contentHash": "u3rLxIwi/9MqDFaWGE/QQgLR1NBEzLOW2lv5+9OrZPDBYIAmFdYSWCWrR1ufpXWOqFn+x02TgKropl/oDuHmgA==" + }, + "System.IO.Hashing": { + "type": "Transitive", + "resolved": "10.0.12", + "contentHash": "jDix4bBMYnpZdSPcnY+KDV6ik3SRMzpMKby/bZl/XUwIiflwRNAFZ0oOl61R/pSaveIJ8t1gs2BUlrGsPs/bcg==" + } + } + } +} \ No newline at end of file diff --git a/eng/test_candidate_audit.py b/eng/test_candidate_audit.py index ed08df0..b766cb5 100644 --- a/eng/test_candidate_audit.py +++ b/eng/test_candidate_audit.py @@ -20,7 +20,8 @@ def test_candidate_keeps_publication_disabled_and_distinct_from_history(self): self.assertIs(candidate["publication_enabled"], False) self.assertIs(preserved["publication_enabled"], False) self.assertNotEqual(candidate["version"], preserved["version"]) - self.assertEqual(candidate["supersedes_candidate"]["source_commit"], preserved["release_source_commit"]) + historical = candidate.get("preserved_phase3_candidate", candidate["supersedes_candidate"]) + self.assertEqual(historical["source_commit"], preserved["release_source_commit"]) self.assertTrue((audit.ROOT / candidate["notes_file"]).is_file()) def test_package_rejects_wrong_provenance_license_and_missing_satellite(self): @@ -53,6 +54,17 @@ def check(files, metadata): return audit.audit_package(package, folder, candidate) check(entries, xml) + candidate['dependency_profile'] = 'process-compat-v1' + process_xml = xml.replace('id="CliWrap" version="3.10.5"', 'id="System.Management" version="10.0.10"') + check(entries, process_xml) + with self.assertRaises(AssertionError): + check(entries, xml) + candidate['dependency_profile'] = 'unknown' + with self.assertRaisesRegex(AssertionError, 'Unknown candidate dependency profile'): + check(entries, process_xml) + del candidate['dependency_profile'] + with self.assertRaises(AssertionError): + check(entries, process_xml) for invalid_xml in (xml.replace(candidate['source_commit'], "b" * 40), xml.replace('>MIT<', '>GPL-3.0-only<')): with self.assertRaises(AssertionError): check(entries, invalid_xml) diff --git a/eng/test_candidate_preflight.py b/eng/test_candidate_preflight.py index 19c80ca..7b53529 100644 --- a/eng/test_candidate_preflight.py +++ b/eng/test_candidate_preflight.py @@ -10,6 +10,36 @@ class PreflightTests(unittest.TestCase): + def test_rc_requires_process_profile_and_pinned_lock(self): + c = json.loads((ROOT / '.github/release-candidate.json').read_text()) + c.update(version='1.0.0-rc.1', tag='v1.0.0-rc.1', + dependency_profile='process-compat-v1', lock_sha256='a' * 64, + notes_file='docs/releases/nuget-1.0.0-rc.1.md') + validate(c) + for changes in ({'dependency_profile': 'legacy-cliwrap'}, + {'dependency_profile': 'unknown'}, {'lock_sha256': 'bad'}): + with self.assertRaises(AssertionError): + validate(dict(c, **changes)) + + def test_rc_source_requires_all_18_first_attempt_jobs(self): + c = {'source_commit': 'a' * 40, 'dependency_profile': 'process-compat-v1'} + run = {'id': 123, 'head_sha': c['source_commit'], 'event': 'push', 'head_branch': 'main', + 'path': '.github/workflows/ci.yml', 'status': 'completed', 'conclusion': 'success', 'run_attempt': 1} + names = [f'Build and test ({os})' for os in ('ubuntu-latest', 'windows-latest', 'macos-latest')] + names += [f'Real PostgreSQL {major} (Linux/net10.0)' for major in range(10, 19)] + names += [f'Native PostgreSQL 18 ({os}/net10.0)' for os in ('windows-latest', 'macos-latest')] + names += [f'Unpublished candidate audit ({os})' for os in ('ubuntu-latest', 'windows-latest', 'macos-latest')] + names += ['Release preflight / Verify reviewed preview without publication'] + jobs = [{'name': name, 'conclusion': 'success'} for name in names] + check(run, jobs, c) + for index in range(len(jobs)): + with self.assertRaises(AssertionError): + check(run, jobs[:index] + jobs[index + 1:], c) + with self.assertRaisesRegex(AssertionError, 'first-attempt'): + check(dict(run, run_attempt=2), jobs, c) + with self.assertRaisesRegex(AssertionError, 'Duplicate'): + check(run, jobs + [jobs[0]], c) + def test_manifest_identity_and_inputs(self): c = json.loads((ROOT / '.github/release-candidate.json').read_text()) validate(c) diff --git a/eng/validate_candidate.py b/eng/validate_candidate.py index 3bff880..fc81c7c 100644 --- a/eng/validate_candidate.py +++ b/eng/validate_candidate.py @@ -19,6 +19,11 @@ def validate(candidate): assert re.fullmatch(r'[A-Za-z0-9_-]+', candidate['nuget_user']) assert candidate['github_environment'] == 'release' assert isinstance(candidate['publication_enabled'], bool) + profile = candidate.get('dependency_profile', 'legacy-cliwrap') + assert profile in ('legacy-cliwrap', 'process-compat-v1'), 'Unknown dependency profile' + if candidate['version'].startswith('1.0.0-rc.'): + assert profile == 'process-compat-v1', 'RC must use the reviewed Process backend' + assert re.fullmatch(r'[0-9a-f]{64}', candidate.get('lock_sha256', '')), 'RC lock must be pinned' for field in ('title', 'package_release_notes'): value = candidate[field] assert value.strip() and '\n' not in value and '\r' not in value diff --git a/eng/verify_candidate_ci.py b/eng/verify_candidate_ci.py index 4b1a771..77ffca1 100644 --- a/eng/verify_candidate_ci.py +++ b/eng/verify_candidate_ci.py @@ -16,6 +16,12 @@ def check(run, jobs, candidate): assert run['status'] == 'completed' and run['conclusion'] == 'success' required = {f'Build and test ({os})' for os in ('ubuntu-latest', 'windows-latest', 'macos-latest')} required.update(f'Real PostgreSQL {major} (Linux/net10.0)' for major in range(10, 19)) + if candidate.get('dependency_profile') == 'process-compat-v1': + required.update(f'Native PostgreSQL 18 ({os}/net10.0)' for os in ('windows-latest', 'macos-latest')) + required.update(f'Unpublished candidate audit ({os})' for os in ('ubuntu-latest', 'windows-latest', 'macos-latest')) + required.add('Release preflight / Verify reviewed preview without publication') + assert run['run_attempt'] == 1, 'RC source requires first-attempt evidence' + assert len(jobs) == len({job['name'] for job in jobs}), 'Duplicate source CI jobs' assert required <= {job['name'] for job in jobs} assert any(job['name'].startswith('Unpublished candidate audit (') for job in jobs) assert len(jobs) >= 13 and all(job['conclusion'] == 'success' for job in jobs) From 7b1692548ec7121b155258805e84d8e722f4b5fa Mon Sep 17 00:00:00 2001 From: ShutenOishi Date: Mon, 5 Oct 2026 12:20:17 +0900 Subject: [PATCH 2/4] Document failed RC preparation CI before fresh retry --- docs/phase-8-rc1.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/phase-8-rc1.md b/docs/phase-8-rc1.md index a11dfb2..c8f46e9 100644 --- a/docs/phase-8-rc1.md +++ b/docs/phase-8-rc1.md @@ -10,6 +10,27 @@ completed the Process-backend replacement through all 18 CI jobs at attempt 1; [the exact artifact receipt](https://github.com/ShutenOishi/pgcli-sharp/pull/26#issuecomment-5978994921) records Windows 30/3/3 stress and actual three-OS PostgreSQL evidence. +### Preparation CI history / 準備CI履歴 + +The first preparation run, CI `37242487375`, is intentionally retained as a +failed receipt rather than retried into acceptance. Linux/macOS deterministic +jobs and both native PostgreSQL 18 jobs completed successfully. Windows normal +tests completed successfully, but the bounded Windows stress step remained +reported in progress until the job itself ended in failure; GitHub did not +produce a Windows artifact and the job-log endpoint returned `BlobNotFound`. +The nine Linux PostgreSQL jobs, three candidate audits and shared preflight were +therefore skipped by dependency gating. This evidence does not establish a +library regression or runner root cause and does not satisfy ADR-0025's +attempt-1 all-18 gate. A new source commit and fresh CI run are required. + +最初の準備CI `37242487375` は、再実行で合格扱いにせず失敗記録として保持します。 +Linux/macOS通常ジョブとWindows/macOSのnative PostgreSQL 18は成功し、Windowsの +通常テストも成功しました。一方、Windows反復stepは実行中表示のままジョブが失敗終了し、 +Windows成果物は生成されず、ログ取得も `BlobNotFound` でした。このためLinux 10〜18、 +3OS候補監査、preflightは依存関係でskipされています。ライブラリ回帰やrunner原因とは +断定せず、ADR-0025の「attempt 1・全18ジョブ」条件も満たしません。新しいソースcommitで +新規CIを実行し直します。 + 1. Prepare current source documentation, RC notes and fail-closed audit controls. 2. Require fresh final PR and exact-main all-18 CI and actual artifact inspection. 3. Fix that successful source SHA/main CI as `1.0.0-rc.1`, retaining alpha.2 From 7406d1b5b22037e6e521db982e2abab04de04f5e Mon Sep 17 00:00:00 2001 From: ShutenOishi Date: Mon, 5 Oct 2026 12:26:43 +0900 Subject: [PATCH 3/4] Stabilize short session control fixtures on Windows --- .../ProcessSessionRunnerTests.cs | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/tests/PgCliSharp.Tests/ProcessSessionRunnerTests.cs b/tests/PgCliSharp.Tests/ProcessSessionRunnerTests.cs index 9d4d84a..898743e 100644 --- a/tests/PgCliSharp.Tests/ProcessSessionRunnerTests.cs +++ b/tests/PgCliSharp.Tests/ProcessSessionRunnerTests.cs @@ -57,7 +57,7 @@ await session.StandardInput.WriteAsync( [Fact] public async Task Session_Timeout_TerminatesProcessAndThrowsTimeout() { - (string executable, string[] arguments) = GetLongRunningCommand(); + (string executable, string[] arguments) = GetControlLongRunningCommand(); var request = new ProcessSessionStartRequest( executable, arguments, @@ -80,7 +80,7 @@ await Assert.ThrowsAsync( [Fact] public async Task Session_ManualCancellation_TerminatesProcess() { - (string executable, string[] arguments) = GetLongRunningCommand(); + (string executable, string[] arguments) = GetControlLongRunningCommand(); var request = new ProcessSessionStartRequest( executable, arguments, @@ -160,7 +160,7 @@ await Assert.ThrowsAsync( [Fact] public async Task Session_LegacyInputBuffer_AppliesBackpressureAndHonorsWriteCancellation() { - (string executable, string[] arguments) = GetLongRunningCommand(); + (string executable, string[] arguments) = GetOwnedLongRunningCommand(); var request = new ProcessSessionStartRequest( executable, arguments, @@ -191,7 +191,7 @@ await Assert.ThrowsAnyAsync( [Fact] public async Task Session_LegacyInputBuffer_ReleasesBlockedWriterWhenSessionEnds() { - (string executable, string[] arguments) = GetLongRunningCommand(); + (string executable, string[] arguments) = GetOwnedLongRunningCommand(); var request = new ProcessSessionStartRequest( executable, arguments, @@ -377,10 +377,27 @@ public override void SetLength(long value) => throw new NotSupportedException(); } - private static (string Executable, string[] Arguments) GetLongRunningCommand() + private static (string Executable, string[] Arguments) GetControlLongRunningCommand() { - // Cancellation/backpressure tests must own the child's lifetime. A - // five-second ping/sleep can finish before a delayed assertion runs. + // Timeout/cancellation assertions are intentionally much shorter than + // this fixture's lifetime. Using a native command keeps CLR fixture + // startup/early-exit noise out of the control-outcome race itself. + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + string windowsDirectory = Environment.GetFolderPath( + Environment.SpecialFolder.Windows); + return ( + Path.Combine(windowsDirectory, "System32", "ping.exe"), + new[] { "127.0.0.1", "-n", "6" }); + } + + return ("/bin/sleep", new[] { "5" }); + } + + private static (string Executable, string[] Arguments) GetOwnedLongRunningCommand() + { + // Backpressure tests must own the child's lifetime. A five-second + // ping/sleep can finish before a delayed blocked-writer assertion runs. return ManagedTestProcess.Command("wait"); } } From 8b275a4ae369a8a827e6924b9f7e005723c9580d Mon Sep 17 00:00:00 2001 From: ShutenOishi Date: Mon, 5 Oct 2026 12:27:01 +0900 Subject: [PATCH 4/4] Record Windows RC stress fixture failure --- docs/phase-8-rc1.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/phase-8-rc1.md b/docs/phase-8-rc1.md index c8f46e9..85cb17b 100644 --- a/docs/phase-8-rc1.md +++ b/docs/phase-8-rc1.md @@ -31,6 +31,25 @@ Windows成果物は生成されず、ログ取得も `BlobNotFound` でした。 断定せず、ADR-0025の「attempt 1・全18ジョブ」条件も満たしません。新しいソースcommitで 新規CIを実行し直します。 +A second fresh run, CI `37259044557`, made the Windows failure concrete: +the normal net48/net8/net10 suites passed 562/559/559, then the first net48 +stress round reported two control tests completing normally instead of observing +their requested timeout/cancellation. The same class's owned infinite fixture +continued to pass its backpressure cases. The follow-up keeps production code +unchanged and separates fixture purposes: short timeout/cancellation control +tests use a multi-second native OS command, while backpressure tests retain the +owned infinite managed fixture so they cannot naturally expire before delayed +assertions. A new attempt-1 CI is required; this test-fixture correction is not +recorded as proof of a production root cause. + +2回目の新規CI `37259044557` では原因範囲をさらに絞れました。通常の +net48/net8/net10は562/559/559件すべて成功した後、net48反復1回目で +timeout/手動cancelの2件だけが要求した制御結果ではなく正常終了を返しました。 +同じクラスの無期限fixtureを使うバックプレッシャー検証は成功しています。 +本番コードは変更せず、短いtimeout/cancel検証は数秒間動くOSネイティブコマンド、 +遅延し得るバックプレッシャー検証は無期限の管理対象fixture、と役割を分離します。 +改めてattempt 1の全CIを必須とし、このテストfixture修正を本番原因の証明とは扱いません。 + 1. Prepare current source documentation, RC notes and fail-closed audit controls. 2. Require fresh final PR and exact-main all-18 CI and actual artifact inspection. 3. Fix that successful source SHA/main CI as `1.0.0-rc.1`, retaining alpha.2